Merge changes I84f108ba,Ia313f5f3,Id8c29545,I405ad66f into main

* changes:
  STL onStop remove dragController if needed
  Revert^2 "PriorityNestedScrollConnection: make onStop suspendable and add onCancel"
  STL nested scroll keeps priority even if it cannot scroll
  Revert^2 "Simplify PriorityNestedScrollConnection"
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index deb6f13..1ae9ada 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -789,7 +789,6 @@
     min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
-        "com.android.healthfitness",
         "com.android.permission",
         "com.android.nfcservices",
     ],
diff --git a/apct-tests/perftests/tracing/Android.bp b/apct-tests/perftests/tracing/Android.bp
index 08e365b..8814216 100644
--- a/apct-tests/perftests/tracing/Android.bp
+++ b/apct-tests/perftests/tracing/Android.bp
@@ -22,6 +22,7 @@
         "apct-perftests-utils",
         "collector-device-lib",
         "platform-test-annotations",
+        "perfetto_trace_java_protos",
     ],
     test_suites: [
         "device-tests",
diff --git a/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
index f4759b8..7ef8c53 100644
--- a/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
+++ b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
@@ -17,10 +17,12 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.os.ServiceManager.ServiceNotFoundException;
 import android.perftests.utils.Stats;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.protolog.common.IProtoLog;
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
 
@@ -31,6 +33,8 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+import perfetto.protos.ProtologCommon;
+
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -65,24 +69,48 @@
         };
     }
 
+    private IProtoLog mProcessedProtoLogger;
+    private static final String MOCK_TEST_FILE_PATH = "mock/file/path";
+    private static final perfetto.protos.Protolog.ProtoLogViewerConfig VIEWER_CONFIG =
+            perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+                .addGroups(
+                        perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+                                .setId(1)
+                                .setName(TestProtoLogGroup.TEST_GROUP.toString())
+                                .setTag(TestProtoLogGroup.TEST_GROUP.getTag())
+                ).addMessages(
+                        perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+                                .setMessageId(123)
+                                .setMessage("My Test Debug Log Message %b")
+                                .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+                                .setGroupId(1)
+                                .setLocation("com/test/MyTestClass.java:123")
+                ).build();
+
     @BeforeClass
     public static void init() {
         ProtoLog.init(TestProtoLogGroup.values());
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws ServiceNotFoundException {
         TestProtoLogGroup.TEST_GROUP.setLogToProto(mLogToProto);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(mLogToLogcat);
+
+        mProcessedProtoLogger = new ProcessedPerfettoProtoLogImpl(
+                MOCK_TEST_FILE_PATH,
+                () -> new AutoClosableProtoInputStream(VIEWER_CONFIG.toByteArray()),
+                () -> {},
+                TestProtoLogGroup.values()
+        );
     }
 
     @Test
     public void log_Processed_NoArgs() {
-        final var protoLog = ProtoLog.getSingleInstance();
         final var perfMonitor = new PerfMonitor();
 
         while (perfMonitor.keepRunning()) {
-            protoLog.log(
+            mProcessedProtoLogger.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                     0, (Object[]) null);
         }
@@ -90,11 +118,10 @@
 
     @Test
     public void log_Processed_WithArgs() {
-        final var protoLog = ProtoLog.getSingleInstance();
         final var perfMonitor = new PerfMonitor();
 
         while (perfMonitor.keepRunning()) {
-            protoLog.log(
+            mProcessedProtoLogger.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                     0b1110101001010100,
                     new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS
index 676cbc7..f820883 100644
--- a/apex/blobstore/OWNERS
+++ b/apex/blobstore/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 25692
+# Bug component: 1628187
 set noparent
 
 sudheersai@google.com
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 98e53ab..810be8f 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -88,4 +88,11 @@
    namespace: "backstage_power"
    description: "Adjust quota default parameters"
    bug: "347058927"
+}
+
+flag {
+   name: "enforce_quota_policy_to_top_started_jobs"
+   namespace: "backstage_power"
+   description: "Apply the quota policy to jobs started when the app was in TOP state"
+   bug: "374323858"
 }
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 885bad5..37e2fe2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -619,7 +619,7 @@
         }
 
         final int uid = jobStatus.getSourceUid();
-        if (mTopAppCache.get(uid)) {
+        if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) {
             if (DEBUG) {
                 Slog.d(TAG, jobStatus.toShortString() + " is top started job");
             }
@@ -656,7 +656,9 @@
                 timer.stopTrackingJob(jobStatus);
             }
         }
-        mTopStartedJobs.remove(jobStatus);
+        if (!Flags.enforceQuotaPolicyToTopStartedJobs()) {
+            mTopStartedJobs.remove(jobStatus);
+        }
     }
 
     @Override
@@ -767,7 +769,7 @@
 
     /** @return true if the job was started while the app was in the TOP state. */
     private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
-        return mTopStartedJobs.contains(jobStatus);
+        return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus);
     }
 
     /** Returns the maximum amount of time this job could run for. */
@@ -4379,11 +4381,13 @@
     @Override
     public void dumpControllerStateLocked(final IndentingPrintWriter pw,
             final Predicate<JobStatus> predicate) {
-        pw.println("Flags: ");
+        pw.println("Aconfig Flags:");
         pw.println("    " + Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS
                 + ": " + Flags.adjustQuotaDefaultConstants());
         pw.println("    " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS
                 + ": " + Flags.enforceQuotaPolicyToFgsJobs());
+        pw.println("    " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS
+                + ": " + Flags.enforceQuotaPolicyToTopStartedJobs());
         pw.println();
 
         pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
diff --git a/core/api/current.txt b/core/api/current.txt
index 911e7de..a8b9e33 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11240,6 +11240,7 @@
     method public void removeCategory(String);
     method public void removeExtra(String);
     method public void removeFlags(int);
+    method @FlaggedApi("android.security.prevent_intent_redirect") public void removeLaunchSecurityProtection();
     method @NonNull public android.content.Intent replaceExtras(@NonNull android.content.Intent);
     method @NonNull public android.content.Intent replaceExtras(@Nullable android.os.Bundle);
     method public android.content.ComponentName resolveActivity(@NonNull android.content.pm.PackageManager);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1ff8c51..79bea01 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1892,8 +1892,7 @@
   }
 
   @FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
-    ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
-    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+    ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
   }
 
   public static class SoundTrigger.RecognitionEvent {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e7f4dbc..b0a8b1b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -237,6 +237,7 @@
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SafeZipPathValidatorCallback;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.DecorView;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
@@ -2680,7 +2681,10 @@
                     handleUnstableProviderDied((IBinder)msg.obj, false);
                     break;
                 case REQUEST_ASSIST_CONTEXT_EXTRAS:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                            "handleRequestAssistContextExtras");
                     handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
                 case TRANSLUCENT_CONVERSION_COMPLETE:
                     handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1);
@@ -7675,6 +7679,16 @@
                 }
             }
         });
+
+        // Register callback to report native memory metrics post GC cleanup
+        if (Flags.reportPostgcMemoryMetrics() &&
+            com.android.libcore.readonly.Flags.postCleanupApis()) {
+            VMRuntime.addPostCleanupCallback(new Runnable() {
+                @Override public void run() {
+                    MetricsLoggerWrapper.logPostGcMemorySnapshot();
+                }
+            });
+        }
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ed6b851..fb5a12b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -132,6 +132,7 @@
 import com.android.internal.annotations.Immutable;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.pm.RoSystemFeatures;
 import com.android.internal.util.UserIcons;
 
 import dalvik.system.VMRuntime;
@@ -822,6 +823,16 @@
                 @Override
                 public Boolean recompute(HasSystemFeatureQuery query) {
                     try {
+                        // As an optimization, check first to see if the feature was defined at
+                        // compile-time as either available or unavailable.
+                        // TODO(b/203143243): Consider hoisting this optimization out of the cache
+                        // after the trunk stable (build) flag has soaked and more features are
+                        // defined at compile-time.
+                        Boolean maybeHasSystemFeature =
+                                RoSystemFeatures.maybeHasFeature(query.name, query.version);
+                        if (maybeHasSystemFeature != null) {
+                            return maybeHasSystemFeature.booleanValue();
+                        }
                         return ActivityThread.currentActivityThread().getPackageManager().
                             hasSystemFeature(query.name, query.version);
                     } catch (RemoteException e) {
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e4d3baa..acedef0 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -21,11 +21,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.text.TextUtils;
@@ -78,10 +80,15 @@
         public abstract @Nullable R apply(@NonNull Q query);
 
         /**
-         * Return true if a query should not use the cache.  The default implementation
-         * always uses the cache.
+         * Return true if a query should not use the cache. The default implementation returns true
+         * if the process UID differs from the calling UID. This is to prevent a binder caller from
+         * reading a cached value created due to a different binder caller, when processes are
+         * caching on behalf of other processes.
          */
         public boolean shouldBypassCache(@NonNull Q query) {
+            if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) {
+                return Binder.getCallingUid() != Process.myUid();
+            }
             return false;
         }
     };
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index e882bb5..081ce31 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -345,6 +345,15 @@
      */
     public AppCompatTaskInfo appCompatTaskInfo = AppCompatTaskInfo.create();
 
+    /**
+     * The top activity's main window frame if it doesn't match the top activity bounds.
+     * {@code null}, otherwise.
+     *
+     * @hide
+     */
+    @Nullable
+    public Rect topActivityMainWindowFrame;
+
     TaskInfo() {
         // Do nothing
     }
@@ -477,7 +486,8 @@
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
                 && requestedVisibleTypes == that.requestedVisibleTypes
-                && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
+                && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo)
+                && Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame);
     }
 
     /**
@@ -553,6 +563,7 @@
         capturedLinkTimestamp = source.readLong();
         requestedVisibleTypes = source.readInt();
         appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
+        topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR);
     }
 
     /**
@@ -606,6 +617,7 @@
         dest.writeLong(capturedLinkTimestamp);
         dest.writeInt(requestedVisibleTypes);
         dest.writeTypedObject(appCompatTaskInfo, flags);
+        dest.writeTypedObject(topActivityMainWindowFrame, flags);
     }
 
     @Override
@@ -649,6 +661,7 @@
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
                 + " requestedVisibleTypes=" + requestedVisibleTypes
                 + " appCompatTaskInfo=" + appCompatTaskInfo
+                + " topActivityMainWindowFrame=" + topActivityMainWindowFrame
                 + "}";
     }
 }
diff --git a/core/java/android/app/metrics.aconfig b/core/java/android/app/metrics.aconfig
new file mode 100644
index 0000000..488f1c7
--- /dev/null
+++ b/core/java/android/app/metrics.aconfig
@@ -0,0 +1,10 @@
+package: "android.app"
+container: "system"
+
+flag {
+     namespace: "system_performance"
+     name: "report_postgc_memory_metrics"
+     is_exported: false
+     description: "Controls whether to report memory metrics post GC cleanup"
+     bug: "331243037"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 37fa9a26..1d4c18f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -286,4 +286,11 @@
   namespace: "systemui"
   description: "Adds logging for notification/modes backup and restore events"
   bug: "289524803"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "notification_classification_ui"
+  namespace: "systemui"
+  description: "Adds UI for NAS classification of notifications"
+  bug: "367996732"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f719528..e8cec70 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -20,6 +20,7 @@
 import static android.content.ContentProvider.maybeAddUserId;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
+import static android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT;
 import static android.security.Flags.preventIntentRedirect;
 
 import android.Manifest;
@@ -40,7 +41,10 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
 import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -670,6 +674,11 @@
 public class Intent implements Parcelable, Cloneable {
     private static final String TAG = "Intent";
 
+    /** @hide */
+    @ChangeId
+    @Overridable
+    public static final long ENABLE_PREVENT_INTENT_REDIRECT = 29076063L;
+
     private static final String ATTR_ACTION = "action";
     private static final String TAG_CATEGORIES = "categories";
     private static final String ATTR_CATEGORY = "category";
@@ -12240,7 +12249,7 @@
      * @hide
      */
     public void collectExtraIntentKeys() {
-        if (!preventIntentRedirect()) return;
+        if (!isPreventIntentRedirectEnabled()) return;
 
         if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
@@ -12257,6 +12266,14 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public static boolean isPreventIntentRedirectEnabled() {
+        return preventIntentRedirect() && CompatChanges.isChangeEnabled(
+                ENABLE_PREVENT_INTENT_REDIRECT);
+    }
+
     /** @hide */
     public void checkCreatorToken() {
         if (mExtras == null) return;
@@ -12281,6 +12298,20 @@
         mExtras.setIsIntentExtra();
     }
 
+    /**
+     * When an intent comes from another app or component as an embedded extra intent, the system
+     * creates a token to identify the creator of this foreign intent. If this token is missing or
+     * invalid, the system will block the launch of this intent. If it contains a valid token, the
+     * system will perform verification against the creator to block launching target it has no
+     * permission to launch or block it from granting URI access to the tagert it cannot access.
+     * This method provides a way to opt out this feature.
+     */
+    @FlaggedApi(FLAG_PREVENT_INTENT_REDIRECT)
+    public void removeLaunchSecurityProtection() {
+        mExtendedFlags &= ~EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN;
+        removeCreatorTokenInfo();
+    }
+
     public void writeToParcel(Parcel out, int flags) {
         out.writeString8(mAction);
         Uri.writeToParcel(out, mData);
@@ -12331,7 +12362,7 @@
             out.writeInt(0);
         }
 
-        if (preventIntentRedirect()) {
+        if (isPreventIntentRedirectEnabled()) {
             if (mCreatorTokenInfo == null) {
                 out.writeInt(0);
             } else {
@@ -12398,7 +12429,7 @@
             mOriginalIntent = new Intent(in);
         }
 
-        if (preventIntentRedirect()) {
+        if (isPreventIntentRedirectEnabled()) {
             if (in.readInt() != 0) {
                 mCreatorTokenInfo = new CreatorTokenInfo();
                 mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5b38942..5f439b1 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -342,3 +342,11 @@
     bug: "292261144"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "change_launcher_badging"
+    namespace: "package_manager_service"
+    description: "Feature flag to introduce a new way to change the launcher badging."
+    bug: "364760703"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 047d1fa..26ffa11 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -39,3 +39,11 @@
   description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
   bug: "322081563"
 }
+
+flag {
+  name: "screen_off_unlock_udfps"
+  is_exported: true
+  namespace: "biometrics_integration"
+  description: "This flag controls Whether to enable fp unlock when screen turns off on udfps devices"
+  bug: "373792870"
+}
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 47541ca..59a602ca 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.TestApi;
 import android.content.Context;
+import android.hardware.biometrics.Flags;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -41,6 +42,7 @@
     private final Context mContext;
     private final boolean mAlwaysOnByDefault;
     private final boolean mPickupGestureEnabledByDefault;
+    private final boolean mScreenOffUdfpsEnabledByDefault;
 
     /** Copied from android.provider.Settings.Secure since these keys are hidden. */
     private static final String[] DOZE_SETTINGS = {
@@ -68,6 +70,8 @@
         mAlwaysOnByDefault = mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnEnabled);
         mPickupGestureEnabledByDefault =
                 mContext.getResources().getBoolean(R.bool.config_dozePickupGestureEnabled);
+        mScreenOffUdfpsEnabledByDefault =
+                mContext.getResources().getBoolean(R.bool.config_screen_off_udfps_enabled);
     }
 
     /** @hide */
@@ -146,7 +150,9 @@
     /** @hide */
     public boolean screenOffUdfpsEnabled(int user) {
         return !TextUtils.isEmpty(udfpsLongPressSensorType())
-            && boolSettingDefaultOff("screen_off_udfps_enabled", user);
+                && ((mScreenOffUdfpsEnabledByDefault && Flags.screenOffUnlockUdfps())
+                ? boolSettingDefaultOn("screen_off_udfps_enabled", user)
+                : boolSettingDefaultOff("screen_off_udfps_enabled", user));
     }
 
     /** @hide */
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 5ee61bc..2df5418 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -99,6 +99,7 @@
     public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59;
     public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60;
     public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61;
+    public static final int KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY = 62;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -175,7 +176,7 @@
             KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
             KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
             KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
-
+            KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -415,6 +416,8 @@
             case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
             case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+            case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MOVE_TO_NEXT_DISPLAY;
             case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
             case KEY_GESTURE_TYPE_LOCK_SCREEN:
@@ -530,6 +533,8 @@
                 return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT";
             case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
                 return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT";
+            case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY:
+                return "KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY";
             case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
                 return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT";
             case KEY_GESTURE_TYPE_LOCK_SCREEN:
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 22ae676..2ba1078 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -40,6 +40,7 @@
 import android.system.ErrnoException;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.UUID;
@@ -170,17 +171,18 @@
 
     public static SoundTrigger.RecognitionConfig aidl2apiRecognitionConfig(
             RecognitionConfig aidlConfig) {
-        var keyphrases =
-            new SoundTrigger.KeyphraseRecognitionExtra[aidlConfig.phraseRecognitionExtras.length];
-        int i = 0;
+        var keyphrases = new ArrayList<SoundTrigger.KeyphraseRecognitionExtra>(
+            aidlConfig.phraseRecognitionExtras.length);
         for (var extras : aidlConfig.phraseRecognitionExtras) {
-            keyphrases[i++] = aidl2apiPhraseRecognitionExtra(extras);
+            keyphrases.add(aidl2apiPhraseRecognitionExtra(extras));
         }
-        return new SoundTrigger.RecognitionConfig(aidlConfig.captureRequested,
-                false /** allowMultipleTriggers **/,
-                keyphrases,
-                Arrays.copyOf(aidlConfig.data, aidlConfig.data.length),
-                aidl2apiAudioCapabilities(aidlConfig.audioCapabilities));
+        return new SoundTrigger.RecognitionConfig.Builder()
+            .setCaptureRequested(aidlConfig.captureRequested)
+            .setAllowMultipleTriggers(false)
+            .setKeyphrases(keyphrases)
+            .setData(Arrays.copyOf(aidlConfig.data, aidlConfig.data.length))
+            .setAudioCapabilities(aidl2apiAudioCapabilities(aidlConfig.audioCapabilities))
+            .build();
     }
 
     public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 05e91e4..a1e7567 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1529,8 +1529,6 @@
          * config that can be used by
          * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
          *
-         * @deprecated should use builder-based constructor instead.
-         *             TODO(b/368042125): remove this method.
          * @param captureRequested Whether the DSP should capture the trigger sound.
          * @param allowMultipleTriggers Whether the service should restart listening after the DSP
          *                              triggers.
@@ -1538,15 +1536,10 @@
          * @param data Opaque data for use by system applications who know about voice engine
          *             internals, typically during enrollment.
          * @param audioCapabilities Bit field encoding of the AudioCapabilities.
-         *
-         * @hide
          */
-        @Deprecated
-        @SuppressWarnings("Todo")
-        @TestApi
-        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
-                @Nullable byte[] data, int audioCapabilities) {
+        private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+                int audioCapabilities) {
             this.mCaptureRequested = captureRequested;
             this.mAllowMultipleTriggers = allowMultipleTriggers;
             this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1558,6 +1551,7 @@
          * Constructor for {@link RecognitionConfig} without audioCapabilities. The
          * audioCapabilities is set to 0.
          *
+         * @deprecated Use {@link Builder} instead.
          * @param captureRequested Whether the DSP should capture the trigger sound.
          * @param allowMultipleTriggers Whether the service should restart listening after the DSP
          *                              triggers.
@@ -1567,10 +1561,10 @@
          * @hide
          */
         @UnsupportedAppUsage
+        @Deprecated
         @TestApi
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
-                @Nullable byte[] data) {
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
             this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
@@ -1718,7 +1712,7 @@
 
             /**
              * Sets capture requested state.
-             * @param captureRequested The new requested state.
+             * @param captureRequested Whether the DSP should capture the trigger sound.
              * @return the same Builder instance.
              */
             public @NonNull Builder setCaptureRequested(boolean captureRequested) {
@@ -1728,7 +1722,8 @@
 
             /**
              * Sets allow multiple triggers state.
-             * @param allowMultipleTriggers The new allow multiple triggers state.
+             * @param allowMultipleTriggers Whether the service should restart listening after the
+             *                              DSP triggers.
              * @return the same Builder instance.
              */
             public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
@@ -1738,7 +1733,8 @@
 
             /**
              * Sets the keyphrases field.
-             * @param keyphrases The new keyphrases.
+             * @param keyphrases The list of keyphrase specific data associated with this
+             *                   recognition session.
              * @return the same Builder instance.
              */
             public @NonNull Builder setKeyphrases(
@@ -1749,7 +1745,9 @@
 
             /**
              * Sets the data field.
-             * @param data The new data.
+             * @param data Opaque data provided to the DSP associated with this recognition session,
+             *             which is used by system applications who know about voice engine
+             *             internals, typically during enrollment.
              * @return the same Builder instance.
              */
             public @NonNull Builder setData(@Nullable byte[] data) {
@@ -1759,7 +1757,8 @@
 
             /**
              * Sets the audio capabilities field.
-             * @param audioCapabilities The new audio capabilities.
+             * @param audioCapabilities The bit field encoding of the audio capabilities associated
+             *                          with this recognition session.
              * @return the same Builder instance.
              */
             public @NonNull Builder setAudioCapabilities(int audioCapabilities) {
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 46cf016..c0398ce 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -40,9 +40,9 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 1c9be6f..f275714 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -28,13 +28,13 @@
 import android.content.pm.PackageManager;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
-import android.os.Binder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.net.module.util.BinderUtils;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -711,7 +711,7 @@
 
         @Override
         public void onPolicyChanged() {
-            Binder.withCleanCallingIdentity(
+            BinderUtils.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
         }
     }
@@ -734,7 +734,7 @@
 
         @Override
         public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
-            Binder.withCleanCallingIdentity(
+            BinderUtils.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode)));
         }
 
@@ -747,7 +747,7 @@
                 @Nullable String exceptionMessage) {
             final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage);
 
-            Binder.withCleanCallingIdentity(
+            BinderUtils.withCleanCallingIdentity(
                     () ->
                             mExecutor.execute(
                                     () ->
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index edf2c09..16114dd 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -21,10 +21,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.PersistableBundle;
+import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 2e6b09f..c7b2f18 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -29,9 +29,9 @@
 import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import java.util.ArrayList;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index ddcb5c6..6c3c285 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -24,6 +24,8 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
 import com.android.internal.os.MonotonicClock;
@@ -43,7 +45,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis.
@@ -126,6 +130,12 @@
     // Max window size. CursorWindow uses only as much memory as needed.
     private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 20_000_000; // bytes
 
+    /**
+     * Used by tests to ensure all BatteryUsageStats instances are closed.
+     */
+    @VisibleForTesting
+    public static boolean DEBUG_INSTANCE_COUNT;
+
     private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
 
     private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
@@ -153,7 +163,7 @@
     private final List<UserBatteryConsumer> mUserBatteryConsumers;
     private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
     private final BatteryStatsHistory mBatteryStatsHistory;
-    private BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
+    private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
     private CursorWindow mBatteryConsumersCursorWindow;
 
     private BatteryUsageStats(@NonNull Builder builder) {
@@ -873,6 +883,7 @@
 
     @Override
     public void close() throws IOException {
+        onCursorWindowReleased(mBatteryConsumersCursorWindow);
         mBatteryConsumersCursorWindow.close();
         mBatteryConsumersCursorWindow = null;
     }
@@ -880,6 +891,7 @@
     @Override
     protected void finalize() throws Throwable {
         if (mBatteryConsumersCursorWindow != null) {
+            // Do not decrement sOpenCusorWindowCount. All instances should be closed explicitly
             mBatteryConsumersCursorWindow.close();
         }
         super.finalize();
@@ -934,6 +946,7 @@
                 boolean includesPowerStateData, double minConsumedPowerThreshold) {
             mBatteryConsumersCursorWindow =
                     new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
+            onCursorWindowAllocated(mBatteryConsumersCursorWindow);
             mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
                     customPowerComponentNames, includePowerModels, includeProcessStateData,
                     includeScreenStateData, includesPowerStateData);
@@ -996,6 +1009,7 @@
          */
         public void discard() {
             mBatteryConsumersCursorWindow.close();
+            onCursorWindowReleased(mBatteryConsumersCursorWindow);
         }
 
         /**
@@ -1264,4 +1278,50 @@
             }
         }
     }
+
+    @GuardedBy("BatteryUsageStats.class")
+    private static Map<CursorWindow, Exception> sInstances;
+
+    private static void onCursorWindowAllocated(CursorWindow window) {
+        if (!DEBUG_INSTANCE_COUNT) {
+            return;
+        }
+
+        synchronized (BatteryUsageStats.class) {
+            if (sInstances == null) {
+                sInstances = new HashMap<>();
+            }
+            sInstances.put(window, new Exception());
+        }
+    }
+
+    private static void onCursorWindowReleased(CursorWindow window) {
+        if (!DEBUG_INSTANCE_COUNT) {
+            return;
+        }
+
+        synchronized (BatteryUsageStats.class) {
+            sInstances.remove(window);
+        }
+    }
+
+    /**
+     * Used by tests to ensure all BatteryUsageStats instances are closed.
+     */
+    @VisibleForTesting
+    public static void assertAllInstancesClosed() {
+        if (!DEBUG_INSTANCE_COUNT) {
+            throw new IllegalStateException("DEBUG_INSTANCE_COUNT is false");
+        }
+
+        synchronized (BatteryUsageStats.class) {
+            if (!sInstances.isEmpty()) {
+                Exception callSite = sInstances.entrySet().iterator().next().getValue();
+                int count = sInstances.size();
+                sInstances.clear();
+                throw new IllegalStateException(
+                        "Instances of BatteryUsageStats not closed: " + count, callSite);
+            }
+        }
+    }
 }
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 9419032..9dec867 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -316,9 +316,7 @@
      * @return True if the hardware can control the frequency of the vibrations, otherwise false.
      */
     public boolean hasFrequencyControl() {
-        // We currently can only control frequency of the vibration using the compose PWLE method.
-        return hasCapability(
-                IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        return hasCapability(IVibrator.CAP_FREQUENCY_CONTROL);
     }
 
     /**
@@ -481,7 +479,8 @@
      * @return True if the hardware supports creating envelope effects, false otherwise.
      */
     public boolean areEnvelopeEffectsSupported() {
-        return hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+        return hasCapability(
+                IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
     }
 
     /**
diff --git a/core/java/android/security/forensic/ForensicEvent.aidl b/core/java/android/security/forensic/ForensicEvent.aidl
new file mode 100644
index 0000000..a321fb0
--- /dev/null
+++ b/core/java/android/security/forensic/ForensicEvent.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.forensic;
+
+/** {@hide} */
+parcelable ForensicEvent;
diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java
new file mode 100644
index 0000000..9cbc5ec
--- /dev/null
+++ b/core/java/android/security/forensic/ForensicEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.forensic;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Flags;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * A class that represents a forensic event.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_AFL_API)
+public final class ForensicEvent implements Parcelable {
+    private static final String TAG = "ForensicEvent";
+
+    @NonNull
+    private final String mType;
+
+    @NonNull
+    private final Map<String, String> mKeyValuePairs;
+
+    public static final @NonNull Parcelable.Creator<ForensicEvent> CREATOR =
+            new Parcelable.Creator<>() {
+                public ForensicEvent createFromParcel(Parcel in) {
+                    return new ForensicEvent(in);
+                }
+
+                public ForensicEvent[] newArray(int size) {
+                    return new ForensicEvent[size];
+                }
+            };
+
+    public ForensicEvent(@NonNull String type, @NonNull Map<String, String> keyValuePairs) {
+        mType = type;
+        mKeyValuePairs = keyValuePairs;
+    }
+
+    private ForensicEvent(@NonNull Parcel in) {
+        mType = in.readString();
+        mKeyValuePairs = new ArrayMap<>(in.readInt());
+        in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mType);
+        out.writeInt(mKeyValuePairs.size());
+        out.writeMap(mKeyValuePairs);
+    }
+
+    @FlaggedApi(Flags.FLAG_AFL_API)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "ForensicEvent{"
+                + "mType=" + mType
+                + ", mKeyValuePairs=" + mKeyValuePairs
+                + '}';
+    }
+}
diff --git a/core/java/android/security/forensic/IBackupTransport.aidl b/core/java/android/security/forensic/IBackupTransport.aidl
new file mode 100644
index 0000000..c2cbc83
--- /dev/null
+++ b/core/java/android/security/forensic/IBackupTransport.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.forensic;
+import android.security.forensic.ForensicEvent;
+
+import com.android.internal.infra.AndroidFuture;
+
+/** {@hide} */
+oneway interface IBackupTransport {
+    /**
+     * Initialize the server side.
+     */
+    void initialize(in AndroidFuture<int> resultFuture);
+
+    /**
+     * Send forensic logging data to the backup destination.
+     * The data is a list of ForensicEvent.
+     * The ForensicEvent is an abstract class that represents
+     * different type of events.
+     */
+    void addData(in List<ForensicEvent> events, in AndroidFuture<int> resultFuture);
+
+    /**
+     * Release the binder to the server.
+     */
+    void release(in AndroidFuture<int> resultFuture);
+}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index b593902a9..9bb1039 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -77,4 +77,11 @@
     description: "Prevent intent redirect attacks"
     bug: "361143368"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "prevent_intent_redirect_abort_or_throw_exception"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by aborting or throwing security exception"
+    bug: "361143368"
 }
\ No newline at end of file
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index ccc17ec..2e660fc 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -73,6 +73,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -1513,10 +1514,11 @@
                     "Recognition for the given keyphrase is not supported");
         }
 
-        KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
+        List<KeyphraseRecognitionExtra> recognitionExtra =
+            new ArrayList<KeyphraseRecognitionExtra>(1);
         // TODO: Do we need to do something about the confidence level here?
-        recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(),
-                mKeyphraseMetadata.getRecognitionModeFlags(), 0, new ConfidenceLevel[0]);
+        recognitionExtra.add(new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(),
+            mKeyphraseMetadata.getRecognitionModeFlags(), 0, new ConfidenceLevel[0]));
         boolean captureTriggerAudio =
                 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
         boolean allowMultipleTriggers =
@@ -1534,10 +1536,17 @@
         int code;
         try {
             code = mSoundTriggerSession.startRecognition(
-                    mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback,
-                    new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                            recognitionExtra, data, audioCapabilities),
-                    runInBatterySaver);
+                mKeyphraseMetadata.getId(),
+                mLocale.toLanguageTag(),
+                mInternalCallback,
+                new RecognitionConfig.Builder()
+                    .setCaptureRequested(captureTriggerAudio)
+                    .setAllowMultipleTriggers(allowMultipleTriggers)
+                    .setKeyphrases(recognitionExtra)
+                    .setData(data)
+                    .setAudioCapabilities(audioCapabilities)
+                    .build(),
+                runInBatterySaver);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e8ef9d6..bce51f2 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1701,6 +1701,11 @@
         public final void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
             // not supported on the deprecated interface - Use TelephonyCallback instead
         }
+
+        public final void onCarrierRoamingNtnAvailableServicesChanged(
+                @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+            // not supported on the deprecated interface - Use TelephonyCallback instead
+        }
     }
 
     private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 5295b60..46e27dc 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -681,6 +681,20 @@
     public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43;
 
     /**
+     * Event for listening to changes in carrier roaming non-terrestrial network available services
+     * via callback onCarrierRoamingNtnAvailableServicesChanged().
+     * This callback is triggered when the available services provided by the carrier roaming
+     * satellite changes. The carrier roaming satellite is defined by the following conditions.
+     * <ul>
+     * <li>Subscription supports attaching to satellite which is defined by
+     * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+     * </ul>
+     *
+     * @hide
+     */
+    public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
+
+    /**
      * @hide
      */
     @IntDef(prefix = {"EVENT_"}, value = {
@@ -726,7 +740,8 @@
             EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
             EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
             EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
-            EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED
+            EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED,
+            EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TelephonyEvent {
@@ -1784,6 +1799,15 @@
          * </ul>
          */
         default void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {}
+
+        /**
+         * Callback invoked when carrier roaming non-terrestrial network available
+         * service changes.
+         *
+         * @param availableServices The list of the supported services.
+         */
+        default void onCarrierRoamingNtnAvailableServicesChanged(
+                @NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
     }
 
     /**
@@ -2235,5 +2259,19 @@
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(
                     () -> listener.onCarrierRoamingNtnEligibleStateChanged(eligible)));
         }
+
+        public void onCarrierRoamingNtnAvailableServicesChanged(
+                @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+            if (!Flags.carrierRoamingNbIotNtn()) return;
+
+            CarrierRoamingNtnModeListener listener =
+                    (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+            if (listener == null) return;
+
+            List<Integer> ServiceList = Arrays.stream(availableServices).boxed()
+                    .collect(Collectors.toList());
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
+        }
     }
 }
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 3c7e924..4d50a45 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -1118,6 +1118,21 @@
     }
 
     /**
+     * Notify external listeners that carrier roaming non-terrestrial available services changed.
+     * @param availableServices The list of the supported services.
+     * @hide
+     */
+    public void notifyCarrierRoamingNtnAvailableServicesChanged(
+            int subId, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+        try {
+            sRegistry.notifyCarrierRoamingNtnAvailableServicesChanged(subId, availableServices);
+        } catch (RemoteException ex) {
+            // system server crash
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Processes potential event changes from the provided {@link TelephonyCallback}.
      *
      * @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1272,12 +1287,9 @@
 
         if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
             eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
-        }
-
-        if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
             eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
+            eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED);
         }
-
         return eventList;
     }
 
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 6448f10..003393c 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -230,7 +230,7 @@
         mRendererWrapper = mSurfaceOnly ? null : renderer;
         mMetricsWrapper = mSurfaceOnly ? null : metrics;
         mViewRoot = mSurfaceOnly ? null : viewRootWrapper;
-        mObserver = mSurfaceOnly
+        mObserver = mSurfaceOnly || (Flags.useSfFrameDuration() && Flags.ignoreHwuiIsFirstFrame())
                 ? null
                 : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
                         mHandler, /* waitForPresentTime= */ false);
@@ -253,6 +253,7 @@
             mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
                 @Override
                 public void surfaceCreated(SurfaceControl.Transaction t) {
+                    Trace.beginSection("FrameTracker#surfaceCreated");
                     mHandler.runWithScissors(() -> {
                         if (mSurfaceControl == null) {
                             mSurfaceControl = mViewRoot.getSurfaceControl();
@@ -262,6 +263,7 @@
                             }
                         }
                     }, EXECUTOR_TASK_TIMEOUT);
+                    Trace.endSection();
                 }
 
                 @Override
@@ -464,23 +466,28 @@
     @Override
     public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
         postCallback(() -> {
-            if (mCancelled || mMetricsFinalized) {
-                return;
-            }
+            try {
+                Trace.beginSection("FrameTracker#onJankDataAvailable");
+                if (mCancelled || mMetricsFinalized) {
+                    return;
+                }
 
-            for (SurfaceControl.JankData jankStat : jankData) {
-                if (!isInRange(jankStat.frameVsyncId)) {
-                    continue;
+                for (SurfaceControl.JankData jankStat : jankData) {
+                    if (!isInRange(jankStat.frameVsyncId)) {
+                        continue;
+                    }
+                    JankInfo info = findJankInfo(jankStat.frameVsyncId);
+                    if (info != null) {
+                        info.update(jankStat);
+                    } else {
+                        mJankInfos.put((int) jankStat.frameVsyncId,
+                                JankInfo.createFromSurfaceControlCallback(jankStat));
+                    }
                 }
-                JankInfo info = findJankInfo(jankStat.frameVsyncId);
-                if (info != null) {
-                    info.update(jankStat);
-                } else {
-                    mJankInfos.put((int) jankStat.frameVsyncId,
-                            JankInfo.createFromSurfaceControlCallback(jankStat));
-                }
+                processJankInfos();
+            } finally {
+                Trace.endSection();
             }
-            processJankInfos();
         });
     }
 
@@ -507,29 +514,35 @@
     @Override
     public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
         postCallback(() -> {
-            if (mCancelled || mMetricsFinalized) {
-                return;
-            }
+            try {
+                Trace.beginSection("FrameTracker#onFrameMetricsAvailable");
+                if (mCancelled || mMetricsFinalized) {
+                    return;
+                }
 
-            // Since this callback might come a little bit late after the end() call.
-            // We should keep tracking the begin / end timestamp that we can compare with
-            // vsync timestamp to check if the frame is in the duration of the CUJ.
-            long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
-            boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
-            long frameVsyncId =
-                    mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
+                // Since this callback might come a little bit late after the end() call.
+                // We should keep tracking the begin / end timestamp that we can compare with
+                // vsync timestamp to check if the frame is in the duration of the CUJ.
+                long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
+                boolean isFirstFrame =
+                    mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
+                long frameVsyncId =
+                        mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
 
-            if (!isInRange(frameVsyncId)) {
-                return;
+                if (!isInRange(frameVsyncId)) {
+                    return;
+                }
+                JankInfo info = findJankInfo(frameVsyncId);
+                if (info != null) {
+                    info.update(totalDurationNanos, isFirstFrame);
+                } else {
+                    mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
+                            frameVsyncId, totalDurationNanos, isFirstFrame));
+                }
+                processJankInfos();
+            } finally {
+                Trace.endSection();
             }
-            JankInfo info = findJankInfo(frameVsyncId);
-            if (info != null) {
-                info.update(totalDurationNanos, isFirstFrame);
-            } else {
-                mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
-                        frameVsyncId, totalDurationNanos, isFirstFrame));
-            }
-            processJankInfos();
         });
     }
 
@@ -568,13 +581,20 @@
     }
 
     private boolean callbacksReceived(JankInfo info) {
-        return mSurfaceOnly
+        return mObserver == null
                 ? info.surfaceControlCallbackFired
                 : info.hwuiCallbackFired && info.surfaceControlCallbackFired;
     }
 
     @UiThread
     private void finish() {
+        Trace.beginSection("FrameTracker#finish");
+        finishTraced();
+        Trace.endSection();
+    }
+
+    @UiThread
+    private void finishTraced() {
         if (mMetricsFinalized || mCancelled) return;
         mMetricsFinalized = true;
 
@@ -599,7 +619,7 @@
         for (int i = 0; i < mJankInfos.size(); i++) {
             JankInfo info = mJankInfos.valueAt(i);
             final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame;
-            if (isFirstDrawn) {
+            if (isFirstDrawn && !Flags.ignoreHwuiIsFirstFrame()) {
                 continue;
             }
             if (info.frameVsyncId > mEndVsyncId) {
@@ -636,7 +656,7 @@
                 }
                 // TODO (b/174755489): Early latch currently gets fired way too often, so we have
                 // to ignore it for now.
-                if (!mSurfaceOnly && !info.hwuiCallbackFired) {
+                if (mObserver != null && !info.hwuiCallbackFired) {
                     markEvent("FT#MissedHWUICallback", info.frameVsyncId);
                     Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
                             + ", CUJ=" + name);
@@ -762,7 +782,9 @@
          * @param observer observer
          */
         public void addObserver(HardwareRendererObserver observer) {
-            mRenderer.addObserver(observer);
+            if (observer != null) {
+                mRenderer.addObserver(observer);
+            }
         }
 
         /**
@@ -770,7 +792,9 @@
          * @param observer observer
          */
         public void removeObserver(HardwareRendererObserver observer) {
-            mRenderer.removeObserver(observer);
+            if (observer != null) {
+                mRenderer.removeObserver(observer);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/jank/flags.aconfig b/core/java/com/android/internal/jank/flags.aconfig
index 82f50ae..287ad18 100644
--- a/core/java/com/android/internal/jank/flags.aconfig
+++ b/core/java/com/android/internal/jank/flags.aconfig
@@ -8,3 +8,10 @@
   bug: "354763298"
   is_fixed_read_only: true
 }
+flag {
+  name: "ignore_hwui_is_first_frame"
+  namespace: "window_surfaces"
+  description: "Whether to remove the usage of the HWUI 'is first frame' flag to ignore jank"
+  bug: "354763298"
+  is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 07aa720..b56aadd 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -1770,6 +1770,10 @@
 
     @GuardedBy("this")
     private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+        if (cur.eventCode != HistoryItem.EVENT_NONE && cur.eventTag.string == null) {
+            Slog.wtfStack(TAG, "Event " + Integer.toHexString(cur.eventCode) + " without a name");
+        }
+
         if (mTracer != null && mTracer.tracingEnabled()) {
             recordTraceEvents(cur.eventCode, cur.eventTag);
             recordTraceCounters(mTraceLastState, cur.states, STATE1_TRACE_MASK,
@@ -2266,6 +2270,7 @@
     private int writeHistoryTag(HistoryTag tag) {
         if (tag.string == null) {
             Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+            tag.string = "";
         }
 
         final int stringLength = tag.string.length();
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index dfb2884..489721f 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -105,7 +105,7 @@
         public void setValues(long[] array) {
             if (array.length != mLength) {
                 throw new IllegalArgumentException(
-                        "Invalid array length: " + mLength + ", expected: " + mLength);
+                        "Invalid array length: " + array.length + ", expected: " + mLength);
             }
             native_setValues(mNativeObject, array);
         }
@@ -116,7 +116,7 @@
         public void getValues(long[] array) {
             if (array.length != mLength) {
                 throw new IllegalArgumentException(
-                        "Invalid array length: " + mLength + ", expected: " + mLength);
+                        "Invalid array length: " + array.length + ", expected: " + mLength);
             }
             native_getValues(mNativeObject, array);
         }
@@ -347,6 +347,11 @@
             throw new IllegalArgumentException(
                     "State: " + state + ", outside the range: [0-" + mStateCount + "]");
         }
+        if (longArrayContainer.mLength != mLength) {
+            throw new IllegalArgumentException(
+                    "Invalid array length: " + longArrayContainer.mLength
+                            + ", expected: " + mLength);
+        }
         native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
     }
 
diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/core/java/com/android/internal/os/ProcfsMemoryUtil.java
similarity index 60%
rename from services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
rename to core/java/com/android/internal/os/ProcfsMemoryUtil.java
index 6cb6dc0..382f6c4 100644
--- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
+++ b/core/java/com/android/internal/os/ProcfsMemoryUtil.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.stats.pull;
+package com.android.internal.os;
 
-import static android.os.Process.PROC_OUT_STRING;
+import static android.os.Process.*;
 
 import android.annotation.Nullable;
 import android.os.Process;
@@ -23,6 +23,7 @@
 
 public final class ProcfsMemoryUtil {
     private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING };
+    private static final int[] OOM_SCORE_ADJ_OUT = new int[] { PROC_NEWLINE_TERM | PROC_OUT_LONG };
     private static final String[] STATUS_KEYS = new String[] {
             "Uid:",
             "VmHWM:",
@@ -38,17 +39,34 @@
     private ProcfsMemoryUtil() {}
 
     /**
-     * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
-     * VmSwap, RssShmem fields in /proc/pid/status in kilobytes or null if not available.
+     * Reads memory stats of a process from procfs.
+     *
+     * Returns values of the VmHWM, VmRss, AnonRSS, VmSwap, RssShmem fields in
+     * /proc/pid/status in kilobytes or null if not available.
      */
     @Nullable
     public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        return readMemorySnapshotFromProcfs("/proc/" + pid + "/status");
+    }
+
+    /**
+     * Reads memory stats of the current process from procfs.
+     *
+     * Returns values of the VmHWM, VmRss, AnonRSS, VmSwap, RssShmem fields in
+     * /proc/self/status in kilobytes or null if not available.
+     */
+    @Nullable
+    public static MemorySnapshot readMemorySnapshotFromProcfs() {
+        return readMemorySnapshotFromProcfs("/proc/self/status");
+    }
+
+    private static MemorySnapshot readMemorySnapshotFromProcfs(String path) {
         long[] output = new long[STATUS_KEYS.length];
         output[0] = -1;
         output[3] = -1;
         output[4] = -1;
         output[5] = -1;
-        Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
+        Process.readProcLines(path, STATUS_KEYS, output);
         if (output[0] == -1 || output[3] == -1 || output[4] == -1 || output[5] == -1) {
             // Could not open or parse file.
             return null;
@@ -70,14 +88,54 @@
      * if the file is not available.
      */
     public static String readCmdlineFromProcfs(int pid) {
+        return readCmdlineFromProcfs("/proc/" + pid + "/cmdline");
+    }
+
+    /**
+     * Reads cmdline of the current process from procfs.
+     *
+     * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
+     * if the file is not available.
+     */
+    public static String readCmdlineFromProcfs() {
+        return readCmdlineFromProcfs("/proc/self/cmdline");
+    }
+
+    private static String readCmdlineFromProcfs(String path) {
         String[] cmdline = new String[1];
-        if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) {
+        if (!Process.readProcFile(path, CMDLINE_OUT, cmdline, null, null)) {
             return "";
         }
         return cmdline[0];
     }
 
     /**
+     * Reads oom_score_adj of a process from procfs
+     *
+     * Returns content of /proc/pid/oom_score_adj. Defaults to 0 if reading fails.
+     */
+    public static int readOomScoreAdjFromProcfs(int pid) {
+        return readOomScoreAdjFromProcfs("/proc/" + pid + "/oom_score_adj");
+    }
+
+    /**
+     * Reads oom_score_adj of the current process from procfs
+     *
+     * Returns content of /proc/pid/oom_score_adj. Defaults to 0 if reading fails.
+     */
+    public static int readOomScoreAdjFromProcfs() {
+        return readOomScoreAdjFromProcfs("/proc/self/oom_score_adj");
+    }
+
+    private static int readOomScoreAdjFromProcfs(String path) {
+        long[] oom_score_adj = new long[1];
+        if (Process.readProcFile(path, OOM_SCORE_ADJ_OUT, null, oom_score_adj, null)) {
+            return (int)oom_score_adj[0];
+        }
+        return 0;
+    }
+
+    /**
      * Scans all /proc/pid/cmdline entries and returns a mapping between pid and cmdline.
      */
     public static SparseArray<String> getProcessCmdlines() {
@@ -109,7 +167,7 @@
 
     /** Reads and parses selected entries of /proc/vmstat. */
     @Nullable
-    static VmStat readVmStat() {
+    public static VmStat readVmStat() {
         long[] vmstat = new long[VMSTAT_KEYS.length];
         vmstat[0] = -1;
         Process.readProcLines("/proc/vmstat", VMSTAT_KEYS, vmstat);
@@ -121,7 +179,7 @@
         return result;
     }
 
-    static final class VmStat {
+    public static final class VmStat {
         public int oomKillCount;
     }
 }
diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
index b42ea7d..e2237f6 100644
--- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
+++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -16,9 +16,15 @@
 
 package com.android.internal.os.logging;
 
+import android.app.Application;
+import android.os.Process;
+import android.util.Log;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.os.ProcfsMemoryUtil;
 import com.android.internal.util.FrameworkStatsLog;
+import java.util.Collection;
+import libcore.util.NativeAllocationRegistry;
 
 /**
  * Used to wrap different logging calls in one, so that client side code base is clean and more
@@ -49,4 +55,46 @@
             }
         }
     }
+
+    public static void logPostGcMemorySnapshot() {
+        if (!com.android.libcore.Flags.nativeMetrics()) {
+            return;
+        }
+        int pid = Process.myPid();
+        String processName = Application.getProcessName();
+        Collection<NativeAllocationRegistry.Metrics> metrics =
+            NativeAllocationRegistry.getMetrics();
+        int nMetrics = metrics.size();
+
+        String[] classNames = new String[nMetrics];
+        long[] mallocedCount = new long[nMetrics];
+        long[] mallocedBytes = new long[nMetrics];
+        long[] nonmallocedCount = new long[nMetrics];
+        long[] nonmallocedBytes = new long[nMetrics];
+
+        int i = 0;
+        for (NativeAllocationRegistry.Metrics m : metrics) {
+            classNames[i] = m.getClassName();
+            mallocedCount[i] = m.getMallocedCount();
+            mallocedBytes[i] = m.getMallocedBytes();
+            nonmallocedCount[i] = m.getNonmallocedCount();
+            nonmallocedBytes[i] = m.getNonmallocedBytes();
+            i++;
+        }
+
+        ProcfsMemoryUtil.MemorySnapshot m = ProcfsMemoryUtil.readMemorySnapshotFromProcfs();
+        int oom_score_adj = ProcfsMemoryUtil.readOomScoreAdjFromProcfs();
+        FrameworkStatsLog.write(FrameworkStatsLog.POSTGC_MEMORY_SNAPSHOT,
+            m.uid, processName, pid,
+            oom_score_adj,
+            m.rssInKilobytes,
+            m.anonRssInKilobytes,
+            m.swapInKilobytes,
+            m.anonRssInKilobytes + m.swapInKilobytes,
+            classNames,
+            mallocedCount,
+            mallocedBytes,
+            nonmallocedCount,
+            nonmallocedBytes);
+    }
 }
diff --git a/core/java/com/android/internal/protolog/AutoClosableProtoInputStream.java b/core/java/com/android/internal/protolog/AutoClosableProtoInputStream.java
new file mode 100644
index 0000000..1acb34f
--- /dev/null
+++ b/core/java/com/android/internal/protolog/AutoClosableProtoInputStream.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.protolog;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoInputStream;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public final class AutoClosableProtoInputStream implements AutoCloseable {
+    @NonNull
+    private final ProtoInputStream mProtoInputStream;
+    @Nullable
+    private final FileInputStream mFileInputStream;
+
+    public AutoClosableProtoInputStream(@NonNull FileInputStream fileInputStream) {
+        mProtoInputStream = new ProtoInputStream(fileInputStream);
+        mFileInputStream = fileInputStream;
+    }
+
+    public AutoClosableProtoInputStream(@NonNull byte[] input) {
+        mProtoInputStream = new ProtoInputStream(input);
+        mFileInputStream = null;
+    }
+
+    /**
+     * @return the ProtoInputStream this class is wrapping
+     */
+    @NonNull
+    public ProtoInputStream get() {
+        return mProtoInputStream;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (mFileInputStream != null) {
+            mFileInputStream.close();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/protolog/NoViewerConfigProtoLogImpl.java b/core/java/com/android/internal/protolog/NoViewerConfigProtoLogImpl.java
new file mode 100644
index 0000000..15987664
--- /dev/null
+++ b/core/java/com/android/internal/protolog/NoViewerConfigProtoLogImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.protolog;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Class should only be used as a temporary solution to missing viewer config file on device.
+ * In particular this class should only be initialized in Robolectric tests, if it's being used
+ * otherwise please report it.
+ *
+ * @deprecated
+ */
+@Deprecated
+public class NoViewerConfigProtoLogImpl implements IProtoLog {
+    private static final String LOG_TAG = "ProtoLog";
+
+    @Override
+    public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
+            Object[] args) {
+        Log.w(LOG_TAG, "ProtoLogging is not available due to missing viewer config file...");
+        logMessage(logLevel, group.getTag(), "PROTOLOG#" + messageHash + "("
+                + Arrays.stream(args).map(Object::toString).collect(Collectors.joining()) + ")");
+    }
+
+    @Override
+    public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
+        logMessage(logLevel, group.getTag(), TextUtils.formatSimple(messageString, args));
+    }
+
+    @Override
+    public boolean isProtoEnabled() {
+        return false;
+    }
+
+    @Override
+    public int startLoggingToLogcat(String[] groups, ILogger logger) {
+        return 0;
+    }
+
+    @Override
+    public int stopLoggingToLogcat(String[] groups, ILogger logger) {
+        return 0;
+    }
+
+    @Override
+    public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+        return false;
+    }
+
+    @Override
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return List.of();
+    }
+
+    private void logMessage(LogLevel logLevel, String tag, String message) {
+        switch (logLevel) {
+            case VERBOSE -> Log.v(tag, message);
+            case INFO -> Log.i(tag, message);
+            case DEBUG -> Log.d(tag, message);
+            case WARN -> Log.w(tag, message);
+            case ERROR -> Log.e(tag, message);
+            case WTF -> Log.wtf(tag, message);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index a037cb4..a1c987f 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -60,18 +60,16 @@
 import android.util.Log;
 import android.util.LongArray;
 import android.util.Slog;
-import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
 import com.android.internal.protolog.common.ILogger;
 import com.android.internal.protolog.common.IProtoLog;
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogDataType;
 import com.android.internal.protolog.common.LogLevel;
 
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -93,26 +91,18 @@
 /**
  * A service for the ProtoLog logging system.
  */
-public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
+public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
     private static final String LOG_TAG = "ProtoLog";
     public static final String NULL_STRING = "null";
     private final AtomicInteger mTracingInstances = new AtomicInteger();
 
     @NonNull
-    private final ProtoLogDataSource mDataSource;
-    @Nullable
-    private final ProtoLogViewerConfigReader mViewerConfigReader;
-    @Deprecated
-    @Nullable
-    private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+    protected final ProtoLogDataSource mDataSource;
     @NonNull
-    private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+    protected final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
     @NonNull
     private final Runnable mCacheUpdater;
 
-    @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
-    private final IProtoLogConfigurationService mProtoLogConfigurationService;
-
     @NonNull
     private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
     @NonNull
@@ -121,68 +111,15 @@
     private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
 
     private final Lock mBackgroundServiceLock = new ReentrantLock();
-    private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+    protected ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
-    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
-            throws ServiceManager.ServiceNotFoundException {
-        this(null, null, null, () -> {}, groups);
-    }
+    // Set to true once this is ready to accept protolog to logcat requests.
+    private boolean mLogcatReady = false;
 
-    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
-            throws ServiceManager.ServiceNotFoundException {
-        this(null, null, null, cacheUpdater, groups);
-    }
-
-    public PerfettoProtoLogImpl(
-            @NonNull String viewerConfigFilePath,
+    protected PerfettoProtoLogImpl(
             @NonNull Runnable cacheUpdater,
             @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
-        this(viewerConfigFilePath,
-                null,
-                new ProtoLogViewerConfigReader(() -> {
-                    try {
-                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
-                    } catch (FileNotFoundException e) {
-                        throw new RuntimeException(
-                                "Failed to load viewer config file " + viewerConfigFilePath, e);
-                    }
-                }),
-                cacheUpdater, groups);
-    }
-
-    @Deprecated
-    @VisibleForTesting
-    public PerfettoProtoLogImpl(
-            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
-            @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups,
-            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
-            @NonNull ProtoLogConfigurationService configurationService) {
-        this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater,
-                groups, dataSourceBuilder, configurationService);
-    }
-
-    @VisibleForTesting
-    public PerfettoProtoLogImpl(
-            @Nullable String viewerConfigFilePath,
-            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
-            @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups,
-            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
-            @NonNull ProtoLogConfigurationService configurationService) {
-        this(viewerConfigFilePath, null, viewerConfigReader, cacheUpdater,
-                groups, dataSourceBuilder, configurationService);
-    }
-
-    private PerfettoProtoLogImpl(
-            @Nullable String viewerConfigFilePath,
-            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
-            @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
-        this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
-                cacheUpdater, groups,
+        this(cacheUpdater, groups,
                 ProtoLogDataSource::new,
                 android.tracing.Flags.clientSideProtoLogging() ?
                     IProtoLogConfigurationService.Stub.asInterface(
@@ -191,19 +128,11 @@
         );
     }
 
-    private PerfettoProtoLogImpl(
-            @Nullable String viewerConfigFilePath,
-            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
+    protected PerfettoProtoLogImpl(
             @NonNull Runnable cacheUpdater,
             @NonNull IProtoLogGroup[] groups,
             @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
             @Nullable IProtoLogConfigurationService configurationService) {
-        if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) {
-            throw new RuntimeException("Only one of viewerConfigFilePath and "
-                    + "viewerConfigInputStreamProvider can be set");
-        }
-
         mDataSource = dataSourceBuilder.build(
                 this::onTracingInstanceStart,
                 this::onTracingFlush,
@@ -219,55 +148,33 @@
         // for some messages logged right after the construction of this class.
         mDataSource.register(params);
 
-        if (viewerConfigInputStreamProvider == null && viewerConfigFilePath != null) {
-            viewerConfigInputStreamProvider = new ViewerConfigInputStreamProvider() {
-                @NonNull
-                @Override
-                public ProtoInputStream getInputStream() {
-                    try {
-                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
-                    } catch (FileNotFoundException e) {
-                        throw new RuntimeException(
-                                "Failed to load viewer config file " + viewerConfigFilePath, e);
-                    }
-                }
-            };
-        }
-
-        this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
-        this.mViewerConfigReader = viewerConfigReader;
         this.mCacheUpdater = cacheUpdater;
 
         registerGroupsLocally(groups);
 
         if (android.tracing.Flags.clientSideProtoLogging()) {
-            mProtoLogConfigurationService = configurationService;
-            Objects.requireNonNull(mProtoLogConfigurationService,
+            Objects.requireNonNull(configurationService,
                     "A null ProtoLog Configuration Service was provided!");
 
             try {
-                var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
-
-                if (viewerConfigFilePath != null) {
-                    args.setViewerConfigFile(viewerConfigFilePath);
-                }
+                var args = createConfigurationServiceRegisterClientArgs();
 
                 final var groupArgs = Stream.of(groups)
-                        .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+                        .map(group -> new RegisterClientArgs
                                 .GroupConfig(group.name(), group.isLogToLogcat()))
-                        .toArray(ProtoLogConfigurationServiceImpl
-                                .RegisterClientArgs.GroupConfig[]::new);
+                        .toArray(RegisterClientArgs.GroupConfig[]::new);
                 args.setGroups(groupArgs);
 
-                mProtoLogConfigurationService.registerClient(this, args);
+                configurationService.registerClient(this, args);
             } catch (RemoteException e) {
                 throw new RuntimeException("Failed to register ProtoLog client");
             }
-        } else {
-            mProtoLogConfigurationService = null;
         }
     }
 
+    @NonNull
+    protected abstract RegisterClientArgs createConfigurationServiceRegisterClientArgs();
+
     /**
      * Main log method, do not call directly.
      */
@@ -334,9 +241,6 @@
      * @return status code
      */
     public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
-        if (mViewerConfigReader != null) {
-            mViewerConfigReader.loadViewerConfig(groups, logger);
-        }
         return setTextLogging(true, logger, groups);
     }
 
@@ -347,9 +251,6 @@
      * @return status code
      */
     public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
-        if (mViewerConfigReader != null) {
-            mViewerConfigReader.unloadViewerConfig(groups, logger);
-        }
         return setTextLogging(false, logger, groups);
     }
 
@@ -372,21 +273,8 @@
         // we might want to manually specify an id for the group with a collision
         verifyNoCollisionsOrDuplicates(protoLogGroups);
 
-        final var groupsLoggingToLogcat = new ArrayList<String>();
         for (IProtoLogGroup protoLogGroup : protoLogGroups) {
             mLogGroups.put(protoLogGroup.name(), protoLogGroup);
-
-            if (protoLogGroup.isLogToLogcat()) {
-                groupsLoggingToLogcat.add(protoLogGroup.name());
-            }
-        }
-
-        if (mViewerConfigReader != null) {
-            // Load in background to avoid delay in boot process.
-            // The caveat is that any log message that is also logged to logcat will not be
-            // successfully decoded until this completes.
-            mBackgroundLoggingService.execute(() -> mViewerConfigReader
-                    .loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0])));
         }
     }
 
@@ -403,6 +291,10 @@
         }
     }
 
+    protected void readyToLogToLogcat() {
+        mLogcatReady = true;
+    }
+
     /**
      * Responds to a shell command.
      */
@@ -499,57 +391,21 @@
     }
 
     @Deprecated
-    private void dumpViewerConfig() {
-        if (mViewerConfigInputStreamProvider == null) {
-            // No viewer config available
+    abstract void dumpViewerConfig();
+
+    @NonNull
+    abstract String getLogcatMessageString(@NonNull Message message);
+
+    private void logToLogcat(@NonNull String tag, @NonNull LogLevel level, @NonNull Message message,
+            @Nullable Object[] args) {
+        if (!mLogcatReady) {
+            Log.w(LOG_TAG, "Trying to log a protolog message with hash "
+                    + message.getMessageHash() + " to logcat before the service is ready to accept "
+                    + "such requests.");
             return;
         }
 
-        Log.d(LOG_TAG, "Dumping viewer config to trace");
-
-        Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider);
-
-        Log.d(LOG_TAG, "Dumped viewer config to trace");
-    }
-
-    private void logToLogcat(String tag, LogLevel level, Message message,
-            @Nullable Object[] args) {
-        String messageString;
-        if (mViewerConfigReader == null) {
-            messageString = message.getMessage();
-
-            if (messageString == null) {
-                Log.e(LOG_TAG, "Failed to decode message for logcat. "
-                        + "Message not available without ViewerConfig to decode the hash.");
-            }
-        } else {
-            messageString = message.getMessage(mViewerConfigReader);
-
-            if (messageString == null) {
-                Log.e(LOG_TAG, "Failed to decode message for logcat. "
-                        + "Message hash either not available in viewerConfig file or "
-                        + "not loaded into memory from file before decoding.");
-            }
-        }
-
-        if (messageString == null) {
-            StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
-            if (args != null) {
-                builder.append(" args = (");
-                builder.append(String.join(", ", Arrays.stream(args)
-                        .map(it -> {
-                            if (it == null) {
-                                return "null";
-                            } else {
-                                return it.toString();
-                            }
-                        }).toList()));
-                builder.append(")");
-            }
-            messageString = builder.toString();
-            args = new Object[0];
-        }
-
+        String messageString = getLogcatMessageString(message);
         logToLogcat(tag, level, messageString, args);
     }
 
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
new file mode 100644
index 0000000..febe1f3
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.protolog;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
+    private static final String LOG_TAG = "PerfettoProtoLogImpl";
+
+    @NonNull
+    private final ProtoLogViewerConfigReader mViewerConfigReader;
+    @Deprecated
+    @NonNull
+    private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+    @NonNull
+    private final String mViewerConfigFilePath;
+
+    public ProcessedPerfettoProtoLogImpl(
+            @NonNull String viewerConfigFilePath,
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
+        this(viewerConfigFilePath, new ViewerConfigInputStreamProvider() {
+                    @NonNull
+                    @Override
+                    public AutoClosableProtoInputStream getInputStream() {
+                        try {
+                            final var protoFileInputStream =
+                                    new FileInputStream(viewerConfigFilePath);
+                            return new AutoClosableProtoInputStream(protoFileInputStream);
+                        } catch (FileNotFoundException e) {
+                            throw new RuntimeException(
+                                    "Failed to load viewer config file " + viewerConfigFilePath, e);
+                        }
+                    }
+                },
+                cacheUpdater, groups);
+    }
+
+    @VisibleForTesting
+    public ProcessedPerfettoProtoLogImpl(
+            @NonNull String viewerConfigFilePath,
+            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
+        super(cacheUpdater, groups);
+
+        this.mViewerConfigFilePath = viewerConfigFilePath;
+
+        this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
+        this.mViewerConfigReader = new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider);
+
+        loadLogcatGroupsViewerConfig(groups);
+    }
+
+    @VisibleForTesting
+    public ProcessedPerfettoProtoLogImpl(
+            @NonNull String viewerConfigFilePath,
+            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            @NonNull ProtoLogViewerConfigReader viewerConfigReader,
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups,
+            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
+            @Nullable IProtoLogConfigurationService configurationService)
+            throws ServiceManager.ServiceNotFoundException {
+        super(cacheUpdater, groups, dataSourceBuilder, configurationService);
+
+        this.mViewerConfigFilePath = viewerConfigFilePath;
+
+        this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
+        this.mViewerConfigReader = viewerConfigReader;
+
+        loadLogcatGroupsViewerConfig(groups);
+    }
+
+    @NonNull
+    @Override
+    protected RegisterClientArgs createConfigurationServiceRegisterClientArgs() {
+        return new RegisterClientArgs()
+                .setViewerConfigFile(mViewerConfigFilePath);
+    }
+
+    /**
+     * Start text logging
+     * @param groups Groups to start text logging for
+     * @param logger A logger to write status updates to
+     * @return status code
+     */
+    @Override
+    public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+        mViewerConfigReader.loadViewerConfig(groups, logger);
+        return super.startLoggingToLogcat(groups, logger);
+    }
+
+    /**
+     * Stop text logging
+     * @param groups Groups to start text logging for
+     * @param logger A logger to write status updates to
+     * @return status code
+     */
+    @Override
+    public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+        mViewerConfigReader.unloadViewerConfig(groups, logger);
+        return super.stopLoggingToLogcat(groups, logger);
+    }
+
+    @Deprecated
+    @Override
+    void dumpViewerConfig() {
+        Log.d(LOG_TAG, "Dumping viewer config to trace");
+        Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider);
+        Log.d(LOG_TAG, "Dumped viewer config to trace");
+    }
+
+    @NonNull
+    @Override
+    String getLogcatMessageString(@NonNull Message message) {
+        String messageString;
+        messageString = message.getMessage(mViewerConfigReader);
+
+        if (messageString == null) {
+            throw new RuntimeException("Failed to decode message for logcat. "
+                    + "Message hash (" + message.getMessageHash() + ") either not available in "
+                    + "viewerConfig file (" +  mViewerConfigFilePath + ") or "
+                    + "not loaded into memory from file before decoding.");
+        }
+
+        return messageString;
+    }
+
+    private void loadLogcatGroupsViewerConfig(@NonNull IProtoLogGroup[] protoLogGroups) {
+        final var groupsLoggingToLogcat = new ArrayList<String>();
+        for (IProtoLogGroup protoLogGroup : protoLogGroups) {
+            if (protoLogGroup.isLogToLogcat()) {
+                groupsLoggingToLogcat.add(protoLogGroup.name());
+            }
+        }
+
+        // Load in background to avoid delay in boot process.
+        // The caveat is that any log message that is also logged to logcat will not be
+        // successfully decoded until this completes.
+        mBackgroundLoggingService.execute(() -> {
+            mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0]));
+            readyToLogToLogcat();
+        });
+    }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 60213b1..d117e93 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -70,16 +70,16 @@
         // directly to the generated tracing implementations.
         if (android.tracing.Flags.perfettoProtologTracing()) {
             synchronized (sInitLock) {
+                final var allGroups = new HashSet<>(Arrays.stream(groups).toList());
                 if (sProtoLogInstance != null) {
                     // The ProtoLog instance has already been initialized in this process
                     final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
-                    final var allGroups = new HashSet<>(alreadyRegisteredGroups);
-                    allGroups.addAll(Arrays.stream(groups).toList());
-                    groups = allGroups.toArray(new IProtoLogGroup[0]);
+                    allGroups.addAll(alreadyRegisteredGroups);
                 }
 
                 try {
-                    sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+                    sProtoLogInstance = new UnprocessedPerfettoProtoLogImpl(
+                            allGroups.toArray(new IProtoLogGroup[0]));
                 } catch (ServiceManager.ServiceNotFoundException e) {
                     throw new RuntimeException(e);
                 }
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
index 8d37899..e9a8770 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
@@ -379,7 +379,7 @@
             @NonNull String viewerConfigFilePath) {
         Utils.dumpViewerConfig(dataSource, () -> {
             try {
-                return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+                return new AutoClosableProtoInputStream(new FileInputStream(viewerConfigFilePath));
             } catch (FileNotFoundException e) {
                 throw new RuntimeException(
                         "Failed to load viewer config file " + viewerConfigFilePath, e);
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 5d67534..3378d08 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -105,31 +105,10 @@
                     + "viewerConfigPath = " + sViewerConfigPath);
 
             final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
-
             if (android.tracing.Flags.perfettoProtologTracing()) {
-                try {
-                    File f = new File(sViewerConfigPath);
-                    if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
-                        // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
-                        // In some tests the viewer config file might not exist in which we don't
-                        // want to provide config path to the user
-                        Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
-                                + ProtoLogImpl.class.getSimpleName() + ". "
-                                + "Setting up without a viewer config instead...");
-
-                        sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
-                    } else {
-                        sServiceInstance =
-                                new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
-                    }
-                } catch (ServiceManager.ServiceNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
+                sServiceInstance = createProtoLogImpl(groups);
             } else {
-                var protologImpl = new LegacyProtoLogImpl(
-                        sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
-                protologImpl.registerGroups(groups);
-                sServiceInstance = protologImpl;
+                sServiceInstance = createLegacyProtoLogImpl(groups);
             }
 
             sCacheUpdater.run();
@@ -137,6 +116,34 @@
         return sServiceInstance;
     }
 
+    private static IProtoLog createProtoLogImpl(IProtoLogGroup[] groups) {
+        try {
+            File f = new File(sViewerConfigPath);
+            if (!f.exists()) {
+                // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+                // In robolectric tests the viewer config file isn't current available, so we cannot
+                // use the ProcessedPerfettoProtoLogImpl.
+                Log.e(LOG_TAG, "Failed to find viewer config file " + sViewerConfigPath
+                        + " when setting up " + ProtoLogImpl.class.getSimpleName() + ". "
+                        + "ProtoLog will not work here!");
+
+                return new NoViewerConfigProtoLogImpl();
+            } else {
+                return new ProcessedPerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+            }
+        } catch (ServiceManager.ServiceNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static LegacyProtoLogImpl createLegacyProtoLogImpl(IProtoLogGroup[] groups) {
+        var protologImpl = new LegacyProtoLogImpl(
+                sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
+        protologImpl.registerGroups(groups);
+
+        return protologImpl;
+    }
+
     @VisibleForTesting
     public static synchronized void setSingleInstance(@Nullable IProtoLog instance) {
         sServiceInstance = instance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 571fe0b..524f642 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -106,46 +106,47 @@
         long targetGroupId = loadGroupId(group);
 
         final Map<Long, String> hashesForGroup = new TreeMap<>();
-        final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+        try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
+            final var pis = pisWrapper.get();
+            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (pis.getFieldNumber() == (int) MESSAGES) {
+                    final long inMessageToken = pis.start(MESSAGES);
 
-        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            if (pis.getFieldNumber() == (int) MESSAGES) {
-                final long inMessageToken = pis.start(MESSAGES);
-
-                long messageId = 0;
-                String message = null;
-                int groupId = 0;
-                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                    switch (pis.getFieldNumber()) {
-                        case (int) MESSAGE_ID:
-                            messageId = pis.readLong(MESSAGE_ID);
-                            break;
-                        case (int) MESSAGE:
-                            message = pis.readString(MESSAGE);
-                            break;
-                        case (int) GROUP_ID:
-                            groupId = pis.readInt(GROUP_ID);
-                            break;
+                    long messageId = 0;
+                    String message = null;
+                    int groupId = 0;
+                    while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                        switch (pis.getFieldNumber()) {
+                            case (int) MESSAGE_ID:
+                                messageId = pis.readLong(MESSAGE_ID);
+                                break;
+                            case (int) MESSAGE:
+                                message = pis.readString(MESSAGE);
+                                break;
+                            case (int) GROUP_ID:
+                                groupId = pis.readInt(GROUP_ID);
+                                break;
+                        }
                     }
-                }
 
-                if (groupId == 0) {
-                    throw new IOException("Failed to get group id");
-                }
+                    if (groupId == 0) {
+                        throw new IOException("Failed to get group id");
+                    }
 
-                if (messageId == 0) {
-                    throw new IOException("Failed to get message id");
-                }
+                    if (messageId == 0) {
+                        throw new IOException("Failed to get message id");
+                    }
 
-                if (message == null) {
-                    throw new IOException("Failed to get message string");
-                }
+                    if (message == null) {
+                        throw new IOException("Failed to get message string");
+                    }
 
-                if (groupId == targetGroupId) {
-                    hashesForGroup.put(messageId, message);
-                }
+                    if (groupId == targetGroupId) {
+                        hashesForGroup.put(messageId, message);
+                    }
 
-                pis.end(inMessageToken);
+                    pis.end(inMessageToken);
+                }
             }
         }
 
@@ -153,30 +154,32 @@
     }
 
     private long loadGroupId(@NonNull String group) throws IOException {
-        final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+        try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
+            final var pis = pisWrapper.get();
 
-        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            if (pis.getFieldNumber() == (int) GROUPS) {
-                final long inMessageToken = pis.start(GROUPS);
+            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (pis.getFieldNumber() == (int) GROUPS) {
+                    final long inMessageToken = pis.start(GROUPS);
 
-                long groupId = 0;
-                String groupName = null;
-                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                    switch (pis.getFieldNumber()) {
-                        case (int) ID:
-                            groupId = pis.readInt(ID);
-                            break;
-                        case (int) NAME:
-                            groupName = pis.readString(NAME);
-                            break;
+                    long groupId = 0;
+                    String groupName = null;
+                    while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                        switch (pis.getFieldNumber()) {
+                            case (int) ID:
+                                groupId = pis.readInt(ID);
+                                break;
+                            case (int) NAME:
+                                groupName = pis.readString(NAME);
+                                break;
+                        }
                     }
-                }
 
-                if (Objects.equals(groupName, group)) {
-                    return groupId;
-                }
+                    if (Objects.equals(groupName, group)) {
+                        return groupId;
+                    }
 
-                pis.end(inMessageToken);
+                    pis.end(inMessageToken);
+                }
             }
         }
 
diff --git a/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
new file mode 100644
index 0000000..f3fe580
--- /dev/null
+++ b/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.protolog;
+
+import android.annotation.NonNull;
+import android.os.ServiceManager;
+
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+public class UnprocessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
+    public UnprocessedPerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+            throws ServiceManager.ServiceNotFoundException {
+        this(() -> {}, groups);
+    }
+
+    public UnprocessedPerfettoProtoLogImpl(@NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
+        super(cacheUpdater, groups);
+        readyToLogToLogcat();
+    }
+
+    @NonNull
+    @Override
+    protected RegisterClientArgs createConfigurationServiceRegisterClientArgs() {
+        return new RegisterClientArgs();
+    }
+
+    @Override
+    void dumpViewerConfig() {
+        // No-op
+    }
+
+    @NonNull
+    @Override
+    String getLogcatMessageString(@NonNull Message message) {
+        String messageString;
+        messageString = message.getMessage();
+
+        if (messageString == null) {
+            throw new RuntimeException("Failed to decode message for logcat. "
+                    + "Message not available without ViewerConfig to decode the hash.");
+        }
+
+        return messageString;
+    }
+}
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
index 00ef80a..629682c 100644
--- a/core/java/com/android/internal/protolog/Utils.java
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -48,8 +48,8 @@
     public static void dumpViewerConfig(@NonNull ProtoLogDataSource dataSource,
             @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
         dataSource.trace(ctx -> {
-            try {
-                ProtoInputStream pis = viewerConfigInputStreamProvider.getInputStream();
+            try (var pisWrapper = viewerConfigInputStreamProvider.getInputStream()) {
+                final var pis = pisWrapper.get();
 
                 final ProtoOutputStream os = ctx.newTracePacket();
 
diff --git a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
index 14bc8e4..60c9892 100644
--- a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
+++ b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
@@ -17,12 +17,12 @@
 package com.android.internal.protolog;
 
 import android.annotation.NonNull;
-import android.util.proto.ProtoInputStream;
 
 public interface ViewerConfigInputStreamProvider {
     /**
      * @return a ProtoInputStream.
      */
     @NonNull
-    ProtoInputStream getInputStream();
+    AutoClosableProtoInputStream getInputStream();
 }
+
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 81b885a..b5c87868 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -84,4 +84,5 @@
     void onSimultaneousCallingStateChanged(in int[] subIds);
     void onCarrierRoamingNtnModeChanged(in boolean active);
     void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
+    void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index f836cf2..ca75abd 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -123,4 +123,5 @@
     void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason);
     void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
     void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
+    void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4198171..9c92e5c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7175,4 +7175,10 @@
     <string name="identity_check_settings_action"></string>
     <!-- Package for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] -->
     <string name="identity_check_settings_package_name">com\u002eandroid\u002esettings</string>
+
+    <!-- The name of the service for forensic backup transport. -->
+    <string name="config_forensicBackupTransport" translatable="false"></string>
+
+    <!-- Whether to enable fp unlock when screen turns off on udfps devices -->
+    <bool name="config_screen_off_udfps_enabled">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0b2b345..712b994 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5636,4 +5636,10 @@
   <!-- Identity check strings -->
   <java-symbol type="string" name="identity_check_settings_action" />
   <java-symbol type="string" name="identity_check_settings_package_name" />
+
+  <!-- Forensic backup transport -->
+  <java-symbol type="string" name="config_forensicBackupTransport" />
+
+  <!-- Fingerprint screen off unlock config -->
+  <java-symbol type="bool" name="config_screen_off_udfps_enabled" />
 </resources>
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index 04945f3..9099918e 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -71,8 +71,7 @@
         VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
         assertFalse(noCapabilities.hasFrequencyControl());
         VibratorInfo composeAndFrequencyControl = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setCapabilities(
-                        IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .build();
         assertTrue(composeAndFrequencyControl.hasFrequencyControl());
     }
@@ -153,7 +152,8 @@
         VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
         assertFalse(noCapabilities.areEnvelopeEffectsSupported());
         VibratorInfo envelopeEffectCapability = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)
+                .setCapabilities(
+                        IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)
                 .build();
         assertTrue(envelopeEffectCapability.areEnvelopeEffectsSupported());
     }
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 4edf52b..0964aab 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -53,22 +53,9 @@
     name: "use_var_font",
 }
 
-soong_config_module_type {
-    name: "prebuilt_fonts_xml",
-    module_type: "prebuilt_etc",
-    config_namespace: "noto_sans_cjk_config",
-    bool_variables: ["use_var_font"],
-    properties: ["src"],
-}
-
-prebuilt_fonts_xml {
+prebuilt_etc {
     name: "fonts.xml",
     src: "fonts.xml",
-    soong_config_variables: {
-        use_var_font: {
-            src: "fonts_cjkvf.xml",
-        },
-    },
 }
 
 prebuilt_etc {
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
deleted file mode 100644
index ae50da1..0000000
--- a/data/fonts/font_fallback.xml
+++ /dev/null
@@ -1,950 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    In this file, all fonts without names are added to the default list.
-    Fonts are chosen based on a match: full BCP-47 language tag including
-    script, then just language, and finally order (the first font containing
-    the glyph).
-
-    Order of appearance is also the tiebreaker for weight matching. This is
-    the reason why the 900 weights of Roboto precede the 700 weights - we
-    prefer the former when an 800 weight is requested. Since bold spans
-    effectively add 300 to the weight, this ensures that 900 is the bold
-    paired with the 500 weight, ensuring adequate contrast.
-
-
-    The font_fallback.xml defines the list of font used by the system.
-
-    `familyset` node:
-      A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
-      to `familyset` node.
-      The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
-
-    `family` node:
-      A `family` node defines a single font family definition.
-      A font family is a set of fonts for drawing text in various styles such as weight, slant.
-      There are three types of families, default family, named family and locale fallback family.
-
-      The default family is a special family node appeared the first node of the `familyset` node.
-      The default family is used as first priority fallback.
-      Only `name` attribute can be used for default family node. If the `name` attribute is
-      specified, This family will also works as named family.
-
-      The named family is a family that has name attribute. The named family defines a new fallback.
-      For example, if the name attribute is "serif", it creates serif fallback. Developers can
-      access the fallback by using Typeface#create API.
-      The named family can not have attribute other than `name` attribute. The `name` attribute
-      cannot be empty.
-
-      The locale fallback family is a font family that is used for fallback. The fallback family is
-      used when the named family or default family cannot be used. The locale fallback family can
-      have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
-      separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
-      `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
-
-    `alias` node:
-      An `alias` node defines a alias of named family with changing weight offset. An `alias` node
-      can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
-      defines new fallback that has the name of specified `name` attribute. The fallback list is
-      the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
-      is specified, the base weight offset is shifted to the specified value. For example, if the
-      `weight` is 500, the output text is drawn with 500 of weight.
-
-    `font` node:
-      A `font` node defines a single font definition. There are two types of fonts, static font and
-      variable font.
-
-      A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
-      is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
-      valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
-      attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
-      index of the font collection. If this is not specified, it is treated as 0. If the font file
-      is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
-      optional attribute. A PostScript name is used for identifying target of system font update.
-      If this is not specified, the system assumes the filename is same to PostScript name of the
-      font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
-      PostScript name of this font is "Roboto-Regular".
-
-      A variable font can be only defined for the variable font file. A variable font can have
-      `axis` child nodes for specifying axis values. A variable font can have all attribute of
-      static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
-      is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
-      be specified.
-
-      If `supportedAxes` attribute is not specified, this `font` node works as static font of the
-      single instance of variable font specified with `axis` children.
-
-      If `supportedAxes` attribute is specified, the system dynamically create font instance for the
-      given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
-      attribute and `axis` child that has `wght` tag become optional and ignored because it is
-      determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
-      attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
-      ignored.
-
-    `axis` node:
-      An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
-      attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
-      specified in `supportedAxes` attribute, the style value works like a default instance.
--->
-<familyset>
-    <!-- first font is default -->
-    <family name="sans-serif">
-        <font supportedAxes="wght,ital">Roboto-Regular.ttf
-          <axis tag="wdth" stylevalue="100" />
-        </font>
-   </family>
-
-
-    <!-- Note that aliases must come after the fonts they reference. -->
-    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
-    <alias name="sans-serif-light" to="sans-serif" weight="300" />
-    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
-    <alias name="sans-serif-black" to="sans-serif" weight="900" />
-    <alias name="arial" to="sans-serif" />
-    <alias name="helvetica" to="sans-serif" />
-    <alias name="tahoma" to="sans-serif" />
-    <alias name="verdana" to="sans-serif" />
-
-    <family name="sans-serif-condensed">
-      <font supportedAxes="wght,ital">Roboto-Regular.ttf
-        <axis tag="wdth" stylevalue="75" />
-      </font>
-    </family>
-    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
-    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
-
-    <family name="serif">
-        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
-        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
-        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
-    </family>
-    <alias name="serif-bold" to="serif" weight="700" />
-    <alias name="times" to="serif" />
-    <alias name="times new roman" to="serif" />
-    <alias name="palatino" to="serif" />
-    <alias name="georgia" to="serif" />
-    <alias name="baskerville" to="serif" />
-    <alias name="goudy" to="serif" />
-    <alias name="fantasy" to="serif" />
-    <alias name="ITC Stone Serif" to="serif" />
-
-    <family name="monospace">
-        <font weight="400" style="normal">DroidSansMono.ttf</font>
-    </family>
-    <alias name="sans-serif-monospace" to="monospace" />
-    <alias name="monaco" to="monospace" />
-
-    <family name="serif-monospace">
-        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
-    </family>
-    <alias name="courier" to="serif-monospace" />
-    <alias name="courier new" to="serif-monospace" />
-
-    <family name="casual">
-        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
-    </family>
-
-    <family name="cursive">
-      <font supportedAxes="wght">DancingScript-Regular.ttf</font>
-    </family>
-
-    <family name="sans-serif-smallcaps">
-        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
-    </family>
-
-    <family name="source-sans-pro">
-        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
-        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
-        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
-        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
-        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
-        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
-    </family>
-    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
-
-    <family name="roboto-flex">
-        <font supportedAxes="wght">RobotoFlex-Regular.ttf
-          <axis tag="wdth" stylevalue="100" />
-        </font>
-    </family>
-
-    <!-- fallback fonts -->
-    <family lang="und-Arab" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
-            NotoNaskhArabic-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
-    </family>
-    <family lang="und-Arab" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
-            NotoNaskhArabicUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Ethi">
-        <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
-            NotoSansEthiopic-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
-            NotoSerifEthiopic-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Hebr">
-        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
-            NotoSansHebrew-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
-    </family>
-    <family lang="und-Thai" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">
-            NotoSerifThai-Regular.ttf
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
-    </family>
-    <family lang="und-Thai" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
-            NotoSansThaiUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Armn">
-        <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
-            NotoSansArmenian-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
-            NotoSerifArmenian-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Geor,und-Geok">
-        <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
-            NotoSansGeorgian-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
-            NotoSerifGeorgian-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Deva" variant="elegant">
-        <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
-            NotoSansDevanagari-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
-            NotoSerifDevanagari-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Deva" variant="compact">
-        <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
-            NotoSansDevanagariUI-VF.ttf
-        </font>
-    </family>
-
-    <!-- All scripts of India should come after Devanagari, due to shared
-         danda characters.
-    -->
-    <family lang="und-Gujr" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
-            NotoSansGujarati-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
-        <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
-          supportedAxes="wght">
-          NotoSerifGujarati-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Gujr" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
-            NotoSansGujaratiUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Guru" variant="elegant">
-        <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
-            NotoSansGurmukhi-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
-            NotoSerifGurmukhi-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Guru" variant="compact">
-        <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
-            NotoSansGurmukhiUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Taml" variant="elegant">
-        <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
-            NotoSansTamil-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
-            NotoSerifTamil-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Taml" variant="compact">
-        <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
-            NotoSansTamilUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Mlym" variant="elegant">
-        <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
-            NotoSansMalayalam-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
-            NotoSerifMalayalam-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Mlym" variant="compact">
-        <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
-            NotoSansMalayalamUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Beng" variant="elegant">
-        <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
-            NotoSansBengali-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular" supportedAxes="wght">
-            NotoSerifBengali-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Beng" variant="compact">
-        <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
-            NotoSansBengaliUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Telu" variant="elegant">
-        <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
-            NotoSansTelugu-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
-            NotoSerifTelugu-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Telu" variant="compact">
-        <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
-            NotoSansTeluguUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Knda" variant="elegant">
-        <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
-            NotoSansKannada-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
-            NotoSerifKannada-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Knda" variant="compact">
-        <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
-            NotoSansKannadaUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Orya" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
-    </family>
-    <family lang="und-Orya" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
-            NotoSansOriyaUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Sinh" variant="elegant">
-        <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
-            NotoSansSinhala-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular" supportedAxes="wght">
-            NotoSerifSinhala-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Sinh" variant="compact">
-        <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
-            NotoSansSinhalaUI-VF.ttf
-        </font>
-    </family>
-    <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
-    <family lang="und-Khmr" variant="elegant">
-        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="26.0"/>
-        </font>
-        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="39.0"/>
-        </font>
-        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="58.0"/>
-        </font>
-        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="90.0"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="108.0"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="128.0"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="151.0"/>
-        </font>
-        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="169.0"/>
-        </font>
-        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="190.0"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
-    </family>
-    <family lang="und-Khmr" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
-            NotoSansKhmerUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Laoo" variant="elegant">
-        <font weight="400" style="normal">NotoSansLao-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">
-            NotoSerifLao-Regular.ttf
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
-    </family>
-    <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Mymr" variant="elegant">
-        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
-        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
-        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
-    </family>
-    <family lang="und-Mymr" variant="compact">
-        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
-        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
-        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
-    </family>
-    <family lang="und-Thaa">
-        <font weight="400" style="normal" postScriptName="NotoSansThaana">
-            NotoSansThaana-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
-    </family>
-    <family lang="und-Cham">
-        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
-    </family>
-    <family lang="und-Ahom">
-        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
-    </family>
-    <family lang="und-Adlm">
-        <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
-            NotoSansAdlam-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Avst">
-        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
-            NotoSansAvestan-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bali">
-        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
-            NotoSansBalinese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bamu">
-        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Batk">
-        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Brah">
-        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
-            NotoSansBrahmi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bugi">
-        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
-            NotoSansBuginese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Buhd">
-        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cans">
-        <font weight="400" style="normal">
-            NotoSansCanadianAboriginal-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cari">
-        <font weight="400" style="normal" postScriptName="NotoSansCarian">
-            NotoSansCarian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cakm">
-        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
-    </family>
-    <family lang="und-Cher">
-        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
-    </family>
-    <family lang="und-Copt">
-        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
-            NotoSansCoptic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Xsux">
-        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
-            NotoSansCuneiform-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cprt">
-        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
-            NotoSansCypriot-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Dsrt">
-        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
-            NotoSansDeseret-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Egyp">
-        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
-            NotoSansEgyptianHieroglyphs-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Elba">
-        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
-    </family>
-    <family lang="und-Glag">
-        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
-            NotoSansGlagolitic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Goth">
-        <font weight="400" style="normal" postScriptName="NotoSansGothic">
-            NotoSansGothic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Hano">
-        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
-            NotoSansHanunoo-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Armi">
-        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
-            NotoSansImperialAramaic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phli">
-        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
-            NotoSansInscriptionalPahlavi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Prti">
-        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
-            NotoSansInscriptionalParthian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Java">
-        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
-    </family>
-    <family lang="und-Kthi">
-        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
-            NotoSansKaithi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Kali">
-        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
-            NotoSansKayahLi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Khar">
-        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
-            NotoSansKharoshthi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lepc">
-        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
-            NotoSansLepcha-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Limb">
-        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Linb">
-        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
-            NotoSansLinearB-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lisu">
-        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lyci">
-        <font weight="400" style="normal" postScriptName="NotoSansLycian">
-            NotoSansLycian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lydi">
-        <font weight="400" style="normal" postScriptName="NotoSansLydian">
-            NotoSansLydian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Mand">
-        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
-            NotoSansMandaic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Mtei">
-        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
-            NotoSansMeeteiMayek-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Talu">
-        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
-            NotoSansNewTaiLue-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Nkoo">
-        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Ogam">
-        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Olck">
-        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
-            NotoSansOlChiki-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Ital">
-        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
-            NotoSansOldItalic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Xpeo">
-        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
-            NotoSansOldPersian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sarb">
-        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
-            NotoSansOldSouthArabian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Orkh">
-        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
-            NotoSansOldTurkic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Osge">
-        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
-    </family>
-    <family lang="und-Osma">
-        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
-            NotoSansOsmanya-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phnx">
-        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
-            NotoSansPhoenician-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Rjng">
-        <font weight="400" style="normal" postScriptName="NotoSansRejang">
-            NotoSansRejang-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Runr">
-        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Samr">
-        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
-            NotoSansSamaritan-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Saur">
-        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
-            NotoSansSaurashtra-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Shaw">
-        <font weight="400" style="normal" postScriptName="NotoSansShavian">
-            NotoSansShavian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sund">
-        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
-            NotoSansSundanese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sylo">
-        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
-            NotoSansSylotiNagri-Regular.ttf
-        </font>
-    </family>
-    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
-    <family lang="und-Syre">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
-            NotoSansSyriacEstrangela-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Syrn">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
-            NotoSansSyriacEastern-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Syrj">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
-            NotoSansSyriacWestern-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tglg">
-        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
-            NotoSansTagalog-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tagb">
-        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
-            NotoSansTagbanwa-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lana">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
-            NotoSansTaiTham-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tavt">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
-            NotoSansTaiViet-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tibt">
-        <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
-            NotoSerifTibetan-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Tfng">
-        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
-    </family>
-    <family lang="und-Ugar">
-        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
-            NotoSansUgaritic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Vaii">
-        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
-        </font>
-    </family>
-    <family>
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
-    </family>
-    <family lang="zh-Hans">
-        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Regular">
-            NotoSansCJK-Regular.ttc
-        </font>
-        <font weight="400" style="normal" index="2" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="zh-Hant,zh-Bopo">
-        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Regular">
-            NotoSansCJK-Regular.ttc
-        </font>
-        <font weight="400" style="normal" index="3" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="ja">
-        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Regular">
-            NotoSansCJK-Regular.ttc
-        </font>
-        <font weight="400" style="normal" index="0" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="ja">
-        <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
-            NotoSerifHentaigana.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-    </family>
-    <family lang="ko">
-        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
-            NotoSansCJK-Regular.ttc
-        </font>
-        <font weight="400" style="normal" index="1" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="und-Zsye">
-        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
-    </family>
-    <family lang="und-Zsye">
-        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
-    </family>
-    <family lang="und-Zsym">
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
-    </family>
-    <!--
-        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
-        override the East Asian punctuation for Chinese.
-    -->
-    <family lang="und-Tale">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Yiii">
-        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
-    </family>
-    <family lang="und-Mong">
-        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
-            NotoSansMongolian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phag">
-        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
-            NotoSansPhagsPa-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Hluw">
-        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
-    </family>
-    <family lang="und-Bass">
-        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
-    </family>
-    <family lang="und-Bhks">
-        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
-    </family>
-    <family lang="und-Hatr">
-        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
-    </family>
-    <family lang="und-Lina">
-        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
-    </family>
-    <family lang="und-Mani">
-        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
-    </family>
-    <family lang="und-Marc">
-        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
-    </family>
-    <family lang="und-Merc">
-        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
-    </family>
-    <family lang="und-Plrd">
-        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
-    </family>
-    <family lang="und-Mroo">
-        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
-    </family>
-    <family lang="und-Mult">
-        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
-    </family>
-    <family lang="und-Nbat">
-        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
-    </family>
-    <family lang="und-Newa">
-        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
-    </family>
-    <family lang="und-Narb">
-        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
-    </family>
-    <family lang="und-Perm">
-        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
-    </family>
-    <family lang="und-Hmng">
-        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
-    </family>
-    <family lang="und-Palm">
-        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
-    </family>
-    <family lang="und-Pauc">
-        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
-    </family>
-    <family lang="und-Shrd">
-        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
-    </family>
-    <family lang="und-Sora">
-        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
-    </family>
-    <family lang="und-Gong">
-        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
-    </family>
-    <family lang="und-Rohg">
-        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
-    </family>
-    <family lang="und-Khoj">
-        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
-    </family>
-    <family lang="und-Gonm">
-        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
-    </family>
-    <family lang="und-Wcho">
-        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
-    </family>
-    <family lang="und-Wara">
-        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
-    </family>
-    <family lang="und-Gran">
-        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
-    </family>
-    <family lang="und-Modi">
-        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
-    </family>
-    <family lang="und-Dogr">
-        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
-    </family>
-    <family lang="und-Medf">
-        <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
-            NotoSansMedefaidrin-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Soyo">
-        <font postScriptName="NotoSansSoyombo-Regular" supportedAxes="wght">
-            NotoSansSoyombo-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Takr">
-        <font postScriptName="NotoSansTakri-Regular" supportedAxes="wght">
-            NotoSansTakri-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Hmnp">
-        <font postScriptName="NotoSerifHmongNyiakeng-Regular" supportedAxes="wght">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Yezi">
-        <font postScriptName="NotoSerifYezidi-Regular" supportedAxes="wght">
-            NotoSerifYezidi-VF.ttf
-        </font>
-    </family>
-</familyset>
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
deleted file mode 100644
index 407d704..0000000
--- a/data/fonts/font_fallback_cjkvf.xml
+++ /dev/null
@@ -1,966 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    In this file, all fonts without names are added to the default list.
-    Fonts are chosen based on a match: full BCP-47 language tag including
-    script, then just language, and finally order (the first font containing
-    the glyph).
-
-    Order of appearance is also the tiebreaker for weight matching. This is
-    the reason why the 900 weights of Roboto precede the 700 weights - we
-    prefer the former when an 800 weight is requested. Since bold spans
-    effectively add 300 to the weight, this ensures that 900 is the bold
-    paired with the 500 weight, ensuring adequate contrast.
-
-
-    The font_fallback.xml defines the list of font used by the system.
-
-    `familyset` node:
-      A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
-      to `familyset` node.
-      The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
-
-    `family` node:
-      A `family` node defines a single font family definition.
-      A font family is a set of fonts for drawing text in various styles such as weight, slant.
-      There are three types of families, default family, named family and locale fallback family.
-
-      The default family is a special family node appeared the first node of the `familyset` node.
-      The default family is used as first priority fallback.
-      Only `name` attribute can be used for default family node. If the `name` attribute is
-      specified, This family will also works as named family.
-
-      The named family is a family that has name attribute. The named family defines a new fallback.
-      For example, if the name attribute is "serif", it creates serif fallback. Developers can
-      access the fallback by using Typeface#create API.
-      The named family can not have attribute other than `name` attribute. The `name` attribute
-      cannot be empty.
-
-      The locale fallback family is a font family that is used for fallback. The fallback family is
-      used when the named family or default family cannot be used. The locale fallback family can
-      have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
-      separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
-      `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
-
-    `alias` node:
-      An `alias` node defines a alias of named family with changing weight offset. An `alias` node
-      can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
-      defines new fallback that has the name of specified `name` attribute. The fallback list is
-      the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
-      is specified, the base weight offset is shifted to the specified value. For example, if the
-      `weight` is 500, the output text is drawn with 500 of weight.
-
-    `font` node:
-      A `font` node defines a single font definition. There are two types of fonts, static font and
-      variable font.
-
-      A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
-      is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
-      valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
-      attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
-      index of the font collection. If this is not specified, it is treated as 0. If the font file
-      is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
-      optional attribute. A PostScript name is used for identifying target of system font update.
-      If this is not specified, the system assumes the filename is same to PostScript name of the
-      font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
-      PostScript name of this font is "Roboto-Regular".
-
-      A variable font can be only defined for the variable font file. A variable font can have
-      `axis` child nodes for specifying axis values. A variable font can have all attribute of
-      static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
-      is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
-      be specified.
-
-      If `supportedAxes` attribute is not specified, this `font` node works as static font of the
-      single instance of variable font specified with `axis` children.
-
-      If `supportedAxes` attribute is specified, the system dynamically create font instance for the
-      given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
-      attribute and `axis` child that has `wght` tag become optional and ignored because it is
-      determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
-      attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
-      ignored.
-
-    `axis` node:
-      An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
-      attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
-      specified in `supportedAxes` attribute, the style value works like a default instance.
--->
-<familyset>
-    <!-- first font is default -->
-    <family name="sans-serif">
-        <font supportedAxes="wght,ital">Roboto-Regular.ttf
-          <axis tag="wdth" stylevalue="100" />
-        </font>
-   </family>
-
-
-    <!-- Note that aliases must come after the fonts they reference. -->
-    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
-    <alias name="sans-serif-light" to="sans-serif" weight="300" />
-    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
-    <alias name="sans-serif-black" to="sans-serif" weight="900" />
-    <alias name="arial" to="sans-serif" />
-    <alias name="helvetica" to="sans-serif" />
-    <alias name="tahoma" to="sans-serif" />
-    <alias name="verdana" to="sans-serif" />
-
-    <family name="sans-serif-condensed">
-      <font supportedAxes="wght,ital">Roboto-Regular.ttf
-        <axis tag="wdth" stylevalue="75" />
-      </font>
-    </family>
-    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
-    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
-
-    <family name="serif">
-        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
-        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
-        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
-    </family>
-    <alias name="serif-bold" to="serif" weight="700" />
-    <alias name="times" to="serif" />
-    <alias name="times new roman" to="serif" />
-    <alias name="palatino" to="serif" />
-    <alias name="georgia" to="serif" />
-    <alias name="baskerville" to="serif" />
-    <alias name="goudy" to="serif" />
-    <alias name="fantasy" to="serif" />
-    <alias name="ITC Stone Serif" to="serif" />
-
-    <family name="monospace">
-        <font weight="400" style="normal">DroidSansMono.ttf</font>
-    </family>
-    <alias name="sans-serif-monospace" to="monospace" />
-    <alias name="monaco" to="monospace" />
-
-    <family name="serif-monospace">
-        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
-    </family>
-    <alias name="courier" to="serif-monospace" />
-    <alias name="courier new" to="serif-monospace" />
-
-    <family name="casual">
-        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
-    </family>
-
-    <family name="cursive">
-      <font supportedAxes="wght">DancingScript-Regular.ttf</font>
-    </family>
-
-    <family name="sans-serif-smallcaps">
-        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
-    </family>
-
-    <family name="source-sans-pro">
-        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
-        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
-        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
-        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
-        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
-        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
-    </family>
-    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
-
-    <family name="roboto-flex">
-        <font supportedAxes="wght">RobotoFlex-Regular.ttf
-          <axis tag="wdth" stylevalue="100" />
-        </font>
-    </family>
-
-    <!-- fallback fonts -->
-    <family lang="und-Arab" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
-            NotoNaskhArabic-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
-    </family>
-    <family lang="und-Arab" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
-            NotoNaskhArabicUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Ethi">
-        <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
-            NotoSansEthiopic-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
-            NotoSerifEthiopic-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Hebr">
-        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
-            NotoSansHebrew-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
-    </family>
-    <family lang="und-Thai" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">
-            NotoSerifThai-Regular.ttf
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
-    </family>
-    <family lang="und-Thai" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
-            NotoSansThaiUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Armn">
-        <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
-            NotoSansArmenian-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
-            NotoSerifArmenian-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Geor,und-Geok">
-        <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
-            NotoSansGeorgian-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
-            NotoSerifGeorgian-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Deva" variant="elegant">
-        <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
-            NotoSansDevanagari-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
-            NotoSerifDevanagari-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Deva" variant="compact">
-        <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
-            NotoSansDevanagariUI-VF.ttf
-        </font>
-    </family>
-
-    <!-- All scripts of India should come after Devanagari, due to shared
-         danda characters.
-    -->
-    <family lang="und-Gujr" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
-            NotoSansGujarati-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
-        <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
-          supportedAxes="wght">
-          NotoSerifGujarati-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Gujr" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
-            NotoSansGujaratiUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Guru" variant="elegant">
-        <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
-            NotoSansGurmukhi-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
-            NotoSerifGurmukhi-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Guru" variant="compact">
-        <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
-            NotoSansGurmukhiUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Taml" variant="elegant">
-        <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
-            NotoSansTamil-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
-            NotoSerifTamil-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Taml" variant="compact">
-        <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
-            NotoSansTamilUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Mlym" variant="elegant">
-        <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
-            NotoSansMalayalam-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
-            NotoSerifMalayalam-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Mlym" variant="compact">
-        <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
-            NotoSansMalayalamUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Beng" variant="elegant">
-        <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
-            NotoSansBengali-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular" supportedAxes="wght">
-            NotoSerifBengali-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Beng" variant="compact">
-        <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
-            NotoSansBengaliUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Telu" variant="elegant">
-        <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
-            NotoSansTelugu-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
-            NotoSerifTelugu-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Telu" variant="compact">
-        <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
-            NotoSansTeluguUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Knda" variant="elegant">
-        <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
-            NotoSansKannada-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
-            NotoSerifKannada-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Knda" variant="compact">
-        <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
-            NotoSansKannadaUI-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Orya" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
-    </family>
-    <family lang="und-Orya" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
-            NotoSansOriyaUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Sinh" variant="elegant">
-        <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
-            NotoSansSinhala-VF.ttf
-        </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular" supportedAxes="wght">
-            NotoSerifSinhala-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Sinh" variant="compact">
-        <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
-            NotoSansSinhalaUI-VF.ttf
-        </font>
-    </family>
-    <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
-    <family lang="und-Khmr" variant="elegant">
-        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="26.0"/>
-        </font>
-        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="39.0"/>
-        </font>
-        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="58.0"/>
-        </font>
-        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="90.0"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="108.0"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="128.0"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="151.0"/>
-        </font>
-        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="169.0"/>
-        </font>
-        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="190.0"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
-    </family>
-    <family lang="und-Khmr" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
-            NotoSansKhmerUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Laoo" variant="elegant">
-        <font weight="400" style="normal">NotoSansLao-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">
-            NotoSerifLao-Regular.ttf
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
-    </family>
-    <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Mymr" variant="elegant">
-        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
-        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
-        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
-    </family>
-    <family lang="und-Mymr" variant="compact">
-        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
-        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
-        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
-    </family>
-    <family lang="und-Thaa">
-        <font weight="400" style="normal" postScriptName="NotoSansThaana">
-            NotoSansThaana-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
-    </family>
-    <family lang="und-Cham">
-        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
-    </family>
-    <family lang="und-Ahom">
-        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
-    </family>
-    <family lang="und-Adlm">
-        <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
-            NotoSansAdlam-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Avst">
-        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
-            NotoSansAvestan-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bali">
-        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
-            NotoSansBalinese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bamu">
-        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Batk">
-        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Brah">
-        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
-            NotoSansBrahmi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bugi">
-        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
-            NotoSansBuginese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Buhd">
-        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cans">
-        <font weight="400" style="normal">
-            NotoSansCanadianAboriginal-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cari">
-        <font weight="400" style="normal" postScriptName="NotoSansCarian">
-            NotoSansCarian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cakm">
-        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
-    </family>
-    <family lang="und-Cher">
-        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
-    </family>
-    <family lang="und-Copt">
-        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
-            NotoSansCoptic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Xsux">
-        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
-            NotoSansCuneiform-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cprt">
-        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
-            NotoSansCypriot-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Dsrt">
-        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
-            NotoSansDeseret-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Egyp">
-        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
-            NotoSansEgyptianHieroglyphs-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Elba">
-        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
-    </family>
-    <family lang="und-Glag">
-        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
-            NotoSansGlagolitic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Goth">
-        <font weight="400" style="normal" postScriptName="NotoSansGothic">
-            NotoSansGothic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Hano">
-        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
-            NotoSansHanunoo-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Armi">
-        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
-            NotoSansImperialAramaic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phli">
-        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
-            NotoSansInscriptionalPahlavi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Prti">
-        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
-            NotoSansInscriptionalParthian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Java">
-        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
-    </family>
-    <family lang="und-Kthi">
-        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
-            NotoSansKaithi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Kali">
-        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
-            NotoSansKayahLi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Khar">
-        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
-            NotoSansKharoshthi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lepc">
-        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
-            NotoSansLepcha-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Limb">
-        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Linb">
-        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
-            NotoSansLinearB-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lisu">
-        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lyci">
-        <font weight="400" style="normal" postScriptName="NotoSansLycian">
-            NotoSansLycian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lydi">
-        <font weight="400" style="normal" postScriptName="NotoSansLydian">
-            NotoSansLydian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Mand">
-        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
-            NotoSansMandaic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Mtei">
-        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
-            NotoSansMeeteiMayek-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Talu">
-        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
-            NotoSansNewTaiLue-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Nkoo">
-        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Ogam">
-        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Olck">
-        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
-            NotoSansOlChiki-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Ital">
-        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
-            NotoSansOldItalic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Xpeo">
-        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
-            NotoSansOldPersian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sarb">
-        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
-            NotoSansOldSouthArabian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Orkh">
-        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
-            NotoSansOldTurkic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Osge">
-        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
-    </family>
-    <family lang="und-Osma">
-        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
-            NotoSansOsmanya-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phnx">
-        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
-            NotoSansPhoenician-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Rjng">
-        <font weight="400" style="normal" postScriptName="NotoSansRejang">
-            NotoSansRejang-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Runr">
-        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Samr">
-        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
-            NotoSansSamaritan-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Saur">
-        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
-            NotoSansSaurashtra-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Shaw">
-        <font weight="400" style="normal" postScriptName="NotoSansShavian">
-            NotoSansShavian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sund">
-        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
-            NotoSansSundanese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sylo">
-        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
-            NotoSansSylotiNagri-Regular.ttf
-        </font>
-    </family>
-    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
-    <family lang="und-Syre">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
-            NotoSansSyriacEstrangela-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Syrn">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
-            NotoSansSyriacEastern-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Syrj">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
-            NotoSansSyriacWestern-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tglg">
-        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
-            NotoSansTagalog-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tagb">
-        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
-            NotoSansTagbanwa-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lana">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
-            NotoSansTaiTham-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tavt">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
-            NotoSansTaiViet-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tibt">
-        <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
-            NotoSerifTibetan-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Tfng">
-        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
-    </family>
-    <family lang="und-Ugar">
-        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
-            NotoSansUgaritic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Vaii">
-        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
-        </font>
-    </family>
-    <family>
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
-    </family>
-    <family lang="zh-Hans">
-        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"
-            supportedAxes="wght">
-            NotoSansCJK-Regular.ttc
-            <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
-                 for making regular style as default. -->
-            <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="400" style="normal" index="2" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="zh-Hant,zh-Bopo">
-        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"
-            supportedAxes="wght">
-            NotoSansCJK-Regular.ttc
-            <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
-                 for making regular style as default. -->
-            <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="400" style="normal" index="3" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="ja">
-        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"
-            supportedAxes="wght">
-            NotoSansCJK-Regular.ttc
-            <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
-                 for making regular style as default. -->
-            <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="400" style="normal" index="0" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="ja">
-        <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
-            NotoSerifHentaigana.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-    </family>
-    <family lang="ko">
-        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"
-            supportedAxes="wght">
-            NotoSansCJK-Regular.ttc
-            <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
-                 for making regular style as default. -->
-            <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="400" style="normal" index="1" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="und-Zsye">
-        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
-    </family>
-    <family lang="und-Zsye">
-        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
-    </family>
-    <family lang="und-Zsym">
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
-    </family>
-    <!--
-        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
-        override the East Asian punctuation for Chinese.
-    -->
-    <family lang="und-Tale">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Yiii">
-        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
-    </family>
-    <family lang="und-Mong">
-        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
-            NotoSansMongolian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phag">
-        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
-            NotoSansPhagsPa-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Hluw">
-        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
-    </family>
-    <family lang="und-Bass">
-        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
-    </family>
-    <family lang="und-Bhks">
-        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
-    </family>
-    <family lang="und-Hatr">
-        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
-    </family>
-    <family lang="und-Lina">
-        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
-    </family>
-    <family lang="und-Mani">
-        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
-    </family>
-    <family lang="und-Marc">
-        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
-    </family>
-    <family lang="und-Merc">
-        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
-    </family>
-    <family lang="und-Plrd">
-        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
-    </family>
-    <family lang="und-Mroo">
-        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
-    </family>
-    <family lang="und-Mult">
-        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
-    </family>
-    <family lang="und-Nbat">
-        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
-    </family>
-    <family lang="und-Newa">
-        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
-    </family>
-    <family lang="und-Narb">
-        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
-    </family>
-    <family lang="und-Perm">
-        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
-    </family>
-    <family lang="und-Hmng">
-        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
-    </family>
-    <family lang="und-Palm">
-        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
-    </family>
-    <family lang="und-Pauc">
-        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
-    </family>
-    <family lang="und-Shrd">
-        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
-    </family>
-    <family lang="und-Sora">
-        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
-    </family>
-    <family lang="und-Gong">
-        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
-    </family>
-    <family lang="und-Rohg">
-        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
-    </family>
-    <family lang="und-Khoj">
-        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
-    </family>
-    <family lang="und-Gonm">
-        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
-    </family>
-    <family lang="und-Wcho">
-        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
-    </family>
-    <family lang="und-Wara">
-        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
-    </family>
-    <family lang="und-Gran">
-        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
-    </family>
-    <family lang="und-Modi">
-        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
-    </family>
-    <family lang="und-Dogr">
-        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
-    </family>
-    <family lang="und-Medf">
-        <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
-            NotoSansMedefaidrin-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Soyo">
-        <font postScriptName="NotoSansSoyombo-Regular" supportedAxes="wght">
-            NotoSansSoyombo-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Takr">
-        <font postScriptName="NotoSansTakri-Regular" supportedAxes="wght">
-            NotoSansTakri-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Hmnp">
-        <font postScriptName="NotoSerifHmongNyiakeng-Regular" supportedAxes="wght">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-        </font>
-    </family>
-    <family lang="und-Yezi">
-        <font postScriptName="NotoSerifYezidi-Regular" supportedAxes="wght">
-            NotoSerifYezidi-VF.ttf
-        </font>
-    </family>
-</familyset>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index d1aa8e9..8cbc300 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1409,24 +1409,123 @@
         <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
     </family>
     <family lang="zh-Hans">
-        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Regular">
+        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
         </font>
         <font weight="400" style="normal" index="2" fallbackFor="serif"
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
         </font>
     </family>
     <family lang="zh-Hant,zh-Bopo">
-        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Regular">
+        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
         </font>
         <font weight="400" style="normal" index="3" fallbackFor="serif"
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Regular">
+        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
         </font>
         <font weight="400" style="normal" index="0" fallbackFor="serif"
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -1443,8 +1542,41 @@
         </font>
     </family>
     <family lang="ko">
-        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
+        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
         </font>
         <font weight="400" style="normal" index="1" fallbackFor="serif"
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
deleted file mode 100644
index 8cbc300..0000000
--- a/data/fonts/fonts_cjkvf.xml
+++ /dev/null
@@ -1,1795 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    DEPRECATED: This XML file is no longer a source of the font files installed
-    in the system.
-
-    For the device vendors: please add your font configurations to the
-    platform/frameworks/base/data/font_fallback.xml and also add it to this XML
-    file as much as possible for apps that reads this XML file.
-
-    For the application developers: please stop reading this XML file and use
-    android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
-    ASystemFontIterator_open NDK API for getting list of system installed
-    font files.
-
-    WARNING: Parsing of this file by third-party apps is not supported. The
-    file, and the font files it refers to, will be renamed and/or moved out
-    from their respective location in the next Android release, and/or the
-    format or syntax of the file may change significantly. If you parse this
-    file for information about system fonts, do it at your own risk. Your
-    application will almost certainly break with the next major Android
-    release.
-
-    In this file, all fonts without names are added to the default list.
-    Fonts are chosen based on a match: full BCP-47 language tag including
-    script, then just language, and finally order (the first font containing
-    the glyph).
-
-    Order of appearance is also the tiebreaker for weight matching. This is
-    the reason why the 900 weights of Roboto precede the 700 weights - we
-    prefer the former when an 800 weight is requested. Since bold spans
-    effectively add 300 to the weight, this ensures that 900 is the bold
-    paired with the 500 weight, ensuring adequate contrast.
-
-    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
--->
-<familyset version="23">
-    <!-- first font is default -->
-    <family name="sans-serif">
-        <font weight="100" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
-        </font>
-        <font weight="100" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
-        </font>
-   </family>
-
-
-    <!-- Note that aliases must come after the fonts they reference. -->
-    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
-    <alias name="sans-serif-light" to="sans-serif" weight="300" />
-    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
-    <alias name="sans-serif-black" to="sans-serif" weight="900" />
-    <alias name="arial" to="sans-serif" />
-    <alias name="helvetica" to="sans-serif" />
-    <alias name="tahoma" to="sans-serif" />
-    <alias name="verdana" to="sans-serif" />
-
-    <family name="sans-serif-condensed">
-      <font weight="100" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="100" />
-      </font>
-      <font weight="200" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="200" />
-      </font>
-      <font weight="300" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="300" />
-      </font>
-      <font weight="400" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="400" />
-      </font>
-      <font weight="500" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="500" />
-      </font>
-      <font weight="600" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="600" />
-      </font>
-      <font weight="700" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="700" />
-      </font>
-      <font weight="800" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="800" />
-      </font>
-      <font weight="900" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="900" />
-      </font>
-      <font weight="100" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="100" />
-      </font>
-      <font weight="200" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="200" />
-      </font>
-      <font weight="300" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="300" />
-      </font>
-      <font weight="400" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="400" />
-      </font>
-      <font weight="500" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="500" />
-      </font>
-      <font weight="600" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="600" />
-      </font>
-      <font weight="700" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="700" />
-      </font>
-      <font weight="800" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="800" />
-      </font>
-      <font weight="900" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="900" />
-      </font>
-    </family>
-    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
-    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
-
-    <family name="serif">
-        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
-        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
-        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
-    </family>
-    <alias name="serif-bold" to="serif" weight="700" />
-    <alias name="times" to="serif" />
-    <alias name="times new roman" to="serif" />
-    <alias name="palatino" to="serif" />
-    <alias name="georgia" to="serif" />
-    <alias name="baskerville" to="serif" />
-    <alias name="goudy" to="serif" />
-    <alias name="fantasy" to="serif" />
-    <alias name="ITC Stone Serif" to="serif" />
-
-    <family name="monospace">
-        <font weight="400" style="normal">DroidSansMono.ttf</font>
-    </family>
-    <alias name="sans-serif-monospace" to="monospace" />
-    <alias name="monaco" to="monospace" />
-
-    <family name="serif-monospace">
-        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
-    </family>
-    <alias name="courier" to="serif-monospace" />
-    <alias name="courier new" to="serif-monospace" />
-
-    <family name="casual">
-        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
-    </family>
-
-    <family name="cursive">
-      <font weight="400" style="normal">DancingScript-Regular.ttf
-        <axis tag="wght" stylevalue="400" />
-      </font>
-      <font weight="700" style="normal">DancingScript-Regular.ttf
-        <axis tag="wght" stylevalue="700" />
-      </font>
-    </family>
-
-    <family name="sans-serif-smallcaps">
-        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
-    </family>
-
-    <family name="source-sans-pro">
-        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
-        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
-        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
-        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
-        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
-        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
-    </family>
-    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
-
-    <family name="roboto-flex">
-        <font weight="100" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
-        </font>
-        <font weight="100" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
-        </font>
-    </family>
-
-    <!-- fallback fonts -->
-    <family lang="und-Arab" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
-            NotoNaskhArabic-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
-    </family>
-    <family lang="und-Arab" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
-            NotoNaskhArabicUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Ethi">
-        <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Hebr">
-        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
-            NotoSansHebrew-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
-    </family>
-    <family lang="und-Thai" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">
-            NotoSerifThai-Regular.ttf
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
-    </family>
-    <family lang="und-Thai" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
-            NotoSansThaiUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Armn">
-        <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Geor,und-Geok">
-        <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Deva" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Deva" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-
-    <!-- All scripts of India should come after Devanagari, due to shared
-         danda characters.
-    -->
-    <family lang="und-Gujr" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
-            NotoSansGujarati-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Gujr" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
-            NotoSansGujaratiUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Guru" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Guru" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Taml" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Taml" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Mlym" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Mlym" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Beng" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Beng" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Telu" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Telu" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Knda" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Knda" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Orya" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
-    </family>
-    <family lang="und-Orya" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
-            NotoSansOriyaUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Sinh" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Sinh" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Khmr" variant="elegant">
-        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="26.0"/>
-        </font>
-        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="39.0"/>
-        </font>
-        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="58.0"/>
-        </font>
-        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="90.0"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="108.0"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="128.0"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="151.0"/>
-        </font>
-        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="169.0"/>
-        </font>
-        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
-            NotoSansKhmer-VF.ttf
-            <axis tag="wdth" stylevalue="100.0"/>
-            <axis tag="wght" stylevalue="190.0"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
-    </family>
-    <family lang="und-Khmr" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
-            NotoSansKhmerUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Laoo" variant="elegant">
-        <font weight="400" style="normal">NotoSansLao-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif">
-            NotoSerifLao-Regular.ttf
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
-    </family>
-    <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
-    </family>
-    <family lang="und-Mymr" variant="elegant">
-        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
-        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
-        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
-        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
-        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
-    </family>
-    <family lang="und-Mymr" variant="compact">
-        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
-        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
-        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
-    </family>
-    <family lang="und-Thaa">
-        <font weight="400" style="normal" postScriptName="NotoSansThaana">
-            NotoSansThaana-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
-    </family>
-    <family lang="und-Cham">
-        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
-        </font>
-        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
-    </family>
-    <family lang="und-Ahom">
-        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
-    </family>
-    <family lang="und-Adlm">
-        <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Avst">
-        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
-            NotoSansAvestan-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bali">
-        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
-            NotoSansBalinese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bamu">
-        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Batk">
-        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Brah">
-        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
-            NotoSansBrahmi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Bugi">
-        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
-            NotoSansBuginese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Buhd">
-        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cans">
-        <font weight="400" style="normal">
-            NotoSansCanadianAboriginal-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cari">
-        <font weight="400" style="normal" postScriptName="NotoSansCarian">
-            NotoSansCarian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cakm">
-        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
-    </family>
-    <family lang="und-Cher">
-        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
-    </family>
-    <family lang="und-Copt">
-        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
-            NotoSansCoptic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Xsux">
-        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
-            NotoSansCuneiform-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Cprt">
-        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
-            NotoSansCypriot-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Dsrt">
-        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
-            NotoSansDeseret-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Egyp">
-        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
-            NotoSansEgyptianHieroglyphs-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Elba">
-        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
-    </family>
-    <family lang="und-Glag">
-        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
-            NotoSansGlagolitic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Goth">
-        <font weight="400" style="normal" postScriptName="NotoSansGothic">
-            NotoSansGothic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Hano">
-        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
-            NotoSansHanunoo-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Armi">
-        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
-            NotoSansImperialAramaic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phli">
-        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
-            NotoSansInscriptionalPahlavi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Prti">
-        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
-            NotoSansInscriptionalParthian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Java">
-        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
-    </family>
-    <family lang="und-Kthi">
-        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
-            NotoSansKaithi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Kali">
-        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
-            NotoSansKayahLi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Khar">
-        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
-            NotoSansKharoshthi-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lepc">
-        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
-            NotoSansLepcha-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Limb">
-        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Linb">
-        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
-            NotoSansLinearB-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lisu">
-        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lyci">
-        <font weight="400" style="normal" postScriptName="NotoSansLycian">
-            NotoSansLycian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lydi">
-        <font weight="400" style="normal" postScriptName="NotoSansLydian">
-            NotoSansLydian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Mand">
-        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
-            NotoSansMandaic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Mtei">
-        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
-            NotoSansMeeteiMayek-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Talu">
-        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
-            NotoSansNewTaiLue-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Nkoo">
-        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Ogam">
-        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Olck">
-        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
-            NotoSansOlChiki-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Ital">
-        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
-            NotoSansOldItalic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Xpeo">
-        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
-            NotoSansOldPersian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sarb">
-        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
-            NotoSansOldSouthArabian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Orkh">
-        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
-            NotoSansOldTurkic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Osge">
-        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
-    </family>
-    <family lang="und-Osma">
-        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
-            NotoSansOsmanya-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phnx">
-        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
-            NotoSansPhoenician-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Rjng">
-        <font weight="400" style="normal" postScriptName="NotoSansRejang">
-            NotoSansRejang-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Runr">
-        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Samr">
-        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
-            NotoSansSamaritan-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Saur">
-        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
-            NotoSansSaurashtra-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Shaw">
-        <font weight="400" style="normal" postScriptName="NotoSansShavian">
-            NotoSansShavian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sund">
-        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
-            NotoSansSundanese-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Sylo">
-        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
-            NotoSansSylotiNagri-Regular.ttf
-        </font>
-    </family>
-    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
-    <family lang="und-Syre">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
-            NotoSansSyriacEstrangela-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Syrn">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
-            NotoSansSyriacEastern-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Syrj">
-        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
-            NotoSansSyriacWestern-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tglg">
-        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
-            NotoSansTagalog-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tagb">
-        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
-            NotoSansTagbanwa-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Lana">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
-            NotoSansTaiTham-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tavt">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
-            NotoSansTaiViet-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Tibt">
-        <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Tfng">
-        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
-    </family>
-    <family lang="und-Ugar">
-        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
-            NotoSansUgaritic-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Vaii">
-        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
-        </font>
-    </family>
-    <family>
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
-    </family>
-    <family lang="zh-Hans">
-        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="100"/>
-        </font>
-        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="200"/>
-        </font>
-        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="300"/>
-        </font>
-        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="800"/>
-        </font>
-        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="900"/>
-        </font>
-        <font weight="400" style="normal" index="2" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="zh-Hant,zh-Bopo">
-        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="100"/>
-        </font>
-        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="200"/>
-        </font>
-        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="300"/>
-        </font>
-        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="800"/>
-        </font>
-        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="900"/>
-        </font>
-        <font weight="400" style="normal" index="3" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="ja">
-        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="100"/>
-        </font>
-        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="200"/>
-        </font>
-        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="300"/>
-        </font>
-        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="800"/>
-        </font>
-        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="900"/>
-        </font>
-        <font weight="400" style="normal" index="0" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="ja">
-        <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
-            NotoSerifHentaigana.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
-            NotoSerifHentaigana.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="ko">
-        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="100"/>
-        </font>
-        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="200"/>
-        </font>
-        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="300"/>
-        </font>
-        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="800"/>
-        </font>
-        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
-            NotoSansCJK-Regular.ttc
-            <axis tag="wght" stylevalue="900"/>
-        </font>
-        <font weight="400" style="normal" index="1" fallbackFor="serif"
-              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
-        </font>
-    </family>
-    <family lang="und-Zsye" ignore="true">
-        <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
-    </family>
-    <family lang="und-Zsye">
-        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
-    </family>
-    <family lang="und-Zsye">
-        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
-    </family>
-    <family lang="und-Zsym">
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
-    </family>
-    <!--
-        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
-        override the East Asian punctuation for Chinese.
-    -->
-    <family lang="und-Tale">
-        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Yiii">
-        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
-    </family>
-    <family lang="und-Mong">
-        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
-            NotoSansMongolian-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Phag">
-        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
-            NotoSansPhagsPa-Regular.ttf
-        </font>
-    </family>
-    <family lang="und-Hluw">
-        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
-    </family>
-    <family lang="und-Bass">
-        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
-    </family>
-    <family lang="und-Bhks">
-        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
-    </family>
-    <family lang="und-Hatr">
-        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
-    </family>
-    <family lang="und-Lina">
-        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
-    </family>
-    <family lang="und-Mani">
-        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
-    </family>
-    <family lang="und-Marc">
-        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
-    </family>
-    <family lang="und-Merc">
-        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
-    </family>
-    <family lang="und-Plrd">
-        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
-    </family>
-    <family lang="und-Mroo">
-        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
-    </family>
-    <family lang="und-Mult">
-        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
-    </family>
-    <family lang="und-Nbat">
-        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
-    </family>
-    <family lang="und-Newa">
-        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
-    </family>
-    <family lang="und-Narb">
-        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
-    </family>
-    <family lang="und-Perm">
-        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
-    </family>
-    <family lang="und-Hmng">
-        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
-    </family>
-    <family lang="und-Palm">
-        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
-    </family>
-    <family lang="und-Pauc">
-        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
-    </family>
-    <family lang="und-Shrd">
-        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
-    </family>
-    <family lang="und-Sora">
-        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
-    </family>
-    <family lang="und-Gong">
-        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
-    </family>
-    <family lang="und-Rohg">
-        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
-    </family>
-    <family lang="und-Khoj">
-        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
-    </family>
-    <family lang="und-Gonm">
-        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
-    </family>
-    <family lang="und-Wcho">
-        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
-    </family>
-    <family lang="und-Wara">
-        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
-    </family>
-    <family lang="und-Gran">
-        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
-    </family>
-    <family lang="und-Modi">
-        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
-    </family>
-    <family lang="und-Dogr">
-        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
-    </family>
-    <family lang="und-Medf">
-        <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Soyo">
-        <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Takr">
-        <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Hmnp">
-        <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-    <family lang="und-Yezi">
-        <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-    </family>
-</familyset>
diff --git a/keystore/java/android/security/OWNERS b/keystore/java/android/security/OWNERS
index 32759b2..4305286 100644
--- a/keystore/java/android/security/OWNERS
+++ b/keystore/java/android/security/OWNERS
@@ -1,2 +1,2 @@
-per-file *.java,*.aidl = eranm@google.com,pgrafov@google.com,rubinxu@google.com
+per-file *.java,*.aidl = drysdale@google.com,jbires@google.com,pgrafov@google.com,rubinxu@google.com
 per-file KeyStoreManager.java = mpgroover@google.com
diff --git a/keystore/tests/OWNERS b/keystore/tests/OWNERS
index 86c31f4..0f94ddc 100644
--- a/keystore/tests/OWNERS
+++ b/keystore/tests/OWNERS
@@ -1,4 +1,7 @@
+# Android HW Trust team
+drysdale@google.com
+jbires@google.com
+
 # Android Enterprise security team
-eranm@google.com
 pgrafov@google.com
 rubinxu@google.com
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 6ad2f08..220fc6f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -54,6 +54,7 @@
     @NonNull
     private final BackupIdler mBackupIdler = new BackupIdler();
     private boolean mBackupIdlerScheduled;
+    private boolean mSaveEmbeddingState = false;
 
     private final List<ParcelableTaskContainerData> mParcelableTaskContainerDataList =
             new ArrayList<>();
@@ -71,11 +72,32 @@
         }
     }
 
+    void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+        if (mSaveEmbeddingState == saveEmbeddingState) {
+            return;
+        }
+
+        Log.i(TAG, "Set save embedding state: " + saveEmbeddingState);
+        mSaveEmbeddingState = saveEmbeddingState;
+        if (!mSaveEmbeddingState) {
+            removeSavedState();
+            return;
+        }
+
+        if (!hasPendingStateToRestore() && !mController.getTaskContainers().isEmpty()) {
+            scheduleBackup();
+        }
+    }
     /**
      * Schedules a back-up request. It is no-op if there was a request scheduled and not yet
      * completed.
      */
     void scheduleBackup() {
+        if (!mSaveEmbeddingState) {
+            // TODO(b/289875940): enabled internally for broader testing.
+            return;
+        }
+
         if (!mBackupIdlerScheduled) {
             mBackupIdlerScheduled = true;
             Looper.getMainLooper().getQueue().addIdleHandler(mBackupIdler);
@@ -128,7 +150,6 @@
         final List<TaskFragmentInfo> infos = savedState.getParcelableArrayList(
                 KEY_RESTORE_TASK_FRAGMENTS_INFO, TaskFragmentInfo.class);
         for (TaskFragmentInfo info : infos) {
-            if (DEBUG) Log.d(TAG, "Retrieved: " + info);
             mTaskFragmentInfos.put(info.getFragmentToken(), info);
             mPresenter.updateTaskFragmentInfo(info);
         }
@@ -140,6 +161,11 @@
             if (DEBUG) Log.d(TAG, "Retrieved: " + info);
             mTaskFragmentParentInfos.put(info.getTaskId(), info);
         }
+
+        if (DEBUG) {
+            Log.d(TAG, "Retrieved task-fragment info: " + infos.size() + ", task info: "
+                    + parentInfos.size());
+        }
     }
 
     void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
@@ -148,7 +174,6 @@
             final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
             mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
         }
-
         removeSavedState();
     }
 
@@ -190,6 +215,9 @@
         final ArrayMap<String, EmbeddingRule> embeddingRuleMap = new ArrayMap<>();
         for (EmbeddingRule rule : rules) {
             embeddingRuleMap.put(rule.getTag(), rule);
+            if (DEBUG) {
+                Log.d(TAG, "Tag: " + rule.getTag() + " rule: " + rule);
+            }
         }
 
         boolean restoredAny = false;
@@ -201,7 +229,7 @@
                 // has unknown tag, unable to restore.
                 if (DEBUG) {
                     Log.d(TAG, "Rebuilding TaskContainer abort! Unknown Tag. Task#"
-                            + parcelableTaskContainerData.mTaskId);
+                            + parcelableTaskContainerData.mTaskId + ", tags = " + tags);
                 }
                 continue;
             }
@@ -217,7 +245,7 @@
 
             final TaskContainer taskContainer = new TaskContainer(parcelableTaskContainerData,
                     mController, mTaskFragmentInfos);
-            if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer);
+            if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer.getTaskId());
             mController.addTaskContainer(taskContainer.getTaskId(), taskContainer);
 
             for (ParcelableSplitContainerData splitData :
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3368e2e..60e1a50 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2886,6 +2886,18 @@
         return getActiveSplitForContainer(container) != null;
     }
 
+
+    @Override
+    public void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+        if (!Flags.aeBackStackRestore()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            mPresenter.setAutoSaveEmbeddingState(saveEmbeddingState);
+        }
+    }
+
     void scheduleBackup() {
         synchronized (mLock) {
             mPresenter.scheduleBackup();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b498ee2..9a2f32e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -183,6 +183,10 @@
         }
     }
 
+    void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+        mBackupHelper.setAutoSaveEmbeddingState(saveEmbeddingState);
+    }
+
     void scheduleBackup() {
         mBackupHelper.scheduleBackup();
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index b453f1d..6928409 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -48,8 +48,6 @@
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
-import com.android.window.flags.Flags;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -634,11 +632,7 @@
         // pin container.
         updateAlwaysOnTopOverlayIfNecessary();
 
-        // TODO(b/289875940): Making backup-restore as an opt-in solution, before the flag goes
-        //  to next-food.
-        if (Flags.aeBackStackRestore()) {
-            mSplitController.scheduleBackup();
-        }
+        mSplitController.scheduleBackup();
     }
 
     private void updateAlwaysOnTopOverlayIfNecessary() {
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 1260796..b2ac640 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
     <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
     <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" />
+    <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
 
     <application>
         <activity
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
new file mode 100644
index 0000000..2d6df43
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bubbles
+
+import com.android.internal.logging.testing.UiEventLoggerFake.FakeUiEvent
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+
+/** Subclass of [Subject] to simplify verifying [FakeUiEvent] data */
+class UiEventSubject(metadata: FailureMetadata, private val actual: FakeUiEvent) :
+    Subject(metadata, actual) {
+
+    /** Check that [FakeUiEvent] contains the expected data from the [bubble] passed id */
+    fun hasBubbleInfo(bubble: Bubble) {
+        check("uid").that(actual.uid).isEqualTo(bubble.appUid)
+        check("packageName").that(actual.packageName).isEqualTo(bubble.packageName)
+        check("instanceId").that(actual.instanceId).isEqualTo(bubble.instanceId)
+    }
+
+    companion object {
+        @JvmStatic
+        fun assertThat(event: FakeUiEvent): UiEventSubject =
+            Truth.assertAbout(Factory(::UiEventSubject)).that(event)
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
new file mode 100644
index 0000000..af238d0
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bubbles
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.testing.UiEventLoggerFake.FakeUiEvent
+import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.google.common.truth.ExpectFailure.assertThat
+import com.google.common.truth.ExpectFailure.expectFailure
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [UiEventSubject] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UiEventSubjectTest {
+
+    private val uiEventSubjectFactory =
+        Subject.Factory<UiEventSubject, FakeUiEvent> { metadata, actual ->
+            UiEventSubject(metadata, actual)
+        }
+
+    private lateinit var uiEventLoggerFake: UiEventLoggerFake
+
+    @Before
+    fun setUp() {
+        uiEventLoggerFake = UiEventLoggerFake()
+    }
+
+    @Test
+    fun test_bubbleLogEvent_hasBubbleInfo() {
+        val bubble =
+            createBubble(
+                appUid = 1,
+                packageName = "test",
+                instanceId = InstanceId.fakeInstanceId(2),
+            )
+        BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+        val uiEvent = uiEventLoggerFake.logs.first()
+
+        // Check that fields match the expected values
+        assertThat(uiEvent.uid).isEqualTo(1)
+        assertThat(uiEvent.packageName).isEqualTo("test")
+        assertThat(uiEvent.instanceId.id).isEqualTo(2)
+
+        // Check that hasBubbleInfo condition passes
+        assertThat(uiEvent).hasBubbleInfo(bubble)
+    }
+
+    @Test
+    fun test_bubbleLogEvent_uidMismatch() {
+        val bubble =
+            createBubble(
+                appUid = 1,
+                packageName = "test",
+                instanceId = InstanceId.fakeInstanceId(2),
+            )
+        BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+        val uiEvent = uiEventLoggerFake.logs.first()
+
+        // Change uid to have a mismatch
+        val otherBubble = bubble.copy(appUid = 99)
+
+        val failure = expectFailure { test ->
+            test.about(uiEventSubjectFactory).that(uiEvent).hasBubbleInfo(otherBubble)
+        }
+        assertThat(failure).factValue("value of").isEqualTo("uiEvent.uid")
+    }
+
+    @Test
+    fun test_bubbleLogEvent_packageNameMismatch() {
+        val bubble =
+            createBubble(
+                appUid = 1,
+                packageName = "test",
+                instanceId = InstanceId.fakeInstanceId(2),
+            )
+        BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+        val uiEvent = uiEventLoggerFake.logs.first()
+
+        // Change package name to have a mismatch
+        val otherBubble = bubble.copy(packageName = "somethingelse")
+
+        val failure = expectFailure { test ->
+            test.about(uiEventSubjectFactory).that(uiEvent).hasBubbleInfo(otherBubble)
+        }
+        assertThat(failure).factValue("value of").isEqualTo("uiEvent.packageName")
+    }
+
+    @Test
+    fun test_bubbleLogEvent_instanceIdMismatch() {
+        val bubble =
+            createBubble(
+                appUid = 1,
+                packageName = "test",
+                instanceId = InstanceId.fakeInstanceId(2),
+            )
+        BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+        val uiEvent = uiEventLoggerFake.logs.first()
+
+        // Change instance id to have a mismatch
+        val otherBubble = bubble.copy(instanceId = InstanceId.fakeInstanceId(99))
+
+        val failure = expectFailure { test ->
+            test.about(uiEventSubjectFactory).that(uiEvent).hasBubbleInfo(otherBubble)
+        }
+        assertThat(failure).factValue("value of").isEqualTo("uiEvent.instanceId")
+    }
+
+    private fun createBubble(appUid: Int, packageName: String, instanceId: InstanceId): Bubble {
+        return mock(Bubble::class.java).apply {
+            whenever(getAppUid()).thenReturn(appUid)
+            whenever(getPackageName()).thenReturn(packageName)
+            whenever(getInstanceId()).thenReturn(instanceId)
+        }
+    }
+
+    private fun Bubble.copy(
+        appUid: Int = this.appUid,
+        packageName: String = this.packageName,
+        instanceId: InstanceId = this.instanceId,
+    ): Bubble {
+        return createBubble(appUid, packageName, instanceId)
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index fa9d2ba..08d647d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -40,6 +40,7 @@
 import com.android.wm.shell.bubbles.BubbleTaskViewFactory
 import com.android.wm.shell.bubbles.DeviceConfig
 import com.android.wm.shell.bubbles.RegionSamplingProvider
+import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.android.wm.shell.shared.handles.RegionSamplingHelper
@@ -47,16 +48,14 @@
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import java.util.Collections
+import java.util.concurrent.Executor
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
-import java.util.Collections
-import java.util.concurrent.Executor
 
 /** Tests for [BubbleBarExpandedViewTest] */
 @SmallTest
@@ -82,7 +81,7 @@
     private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null
     private var regionSamplingProvider: TestRegionSamplingProvider? = null
 
-    private val bubbleLogger = spy(BubbleLogger(UiEventLoggerFake()))
+    private val uiEventLoggerFake = UiEventLoggerFake()
 
     @Before
     fun setUp() {
@@ -116,7 +115,7 @@
         bubbleExpandedView.initialize(
             expandedViewManager,
             positioner,
-            bubbleLogger,
+            BubbleLogger(uiEventLoggerFake),
             false /* isOverflow */,
             bubbleTaskView,
             mainExecutor,
@@ -223,7 +222,10 @@
             bubbleExpandedView.findViewWithTag<View>(BubbleBarMenuView.DISMISS_ACTION_TAG)
         assertThat(dismissMenuItem).isNotNull()
         getInstrumentation().runOnMainSync { dismissMenuItem.performClick() }
-        verify(bubbleLogger).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.logs[0].eventId)
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU.id)
+        assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
     }
 
     private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 9ca9b73..4569cf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -26,6 +26,7 @@
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
@@ -34,6 +35,8 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 
+import java.lang.ref.WeakReference;
+
 /**
  * Used to register the animation callback and runner, it will trigger result if gesture was finish
  * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
@@ -101,6 +104,40 @@
         return mCallback;
     }
 
+    private Runnable mFinishedCallback;
+    private RemoteAnimationTarget[] mApps;
+    private IRemoteAnimationFinishedCallback mRemoteCallback;
+
+    private static class RemoteAnimationFinishedStub extends IRemoteAnimationFinishedCallback.Stub {
+        //the binder callback should not hold strong reference to it to avoid memory leak.
+        private WeakReference<BackAnimationRunner> mRunnerRef;
+
+        private RemoteAnimationFinishedStub(BackAnimationRunner runner) {
+            mRunnerRef = new WeakReference<>(runner);
+        }
+
+        @Override
+        public void onAnimationFinished() {
+            BackAnimationRunner runner = mRunnerRef.get();
+            if (runner == null) {
+                return;
+            }
+            if (runner.shouldMonitorCUJ(runner.mApps)) {
+                InteractionJankMonitor.getInstance().end(runner.mCujType);
+            }
+
+            runner.mFinishedCallback.run();
+            for (int i = runner.mApps.length - 1; i >= 0; --i) {
+                 SurfaceControl sc = runner.mApps[i].leash;
+                 if (sc != null && sc.isValid()) {
+                     sc.release();
+                 }
+            }
+            runner.mApps = null;
+            runner.mFinishedCallback = null;
+        }
+    }
+
     /**
      * Called from {@link IBackAnimationRunner}, it will deliver these
      * {@link RemoteAnimationTarget}s to the corresponding runner.
@@ -108,16 +145,9 @@
     void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
         InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance();
-        final IRemoteAnimationFinishedCallback callback =
-                new IRemoteAnimationFinishedCallback.Stub() {
-                    @Override
-                    public void onAnimationFinished() {
-                        if (shouldMonitorCUJ(apps)) {
-                            interactionJankMonitor.end(mCujType);
-                        }
-                        finishedCallback.run();
-                    }
-                };
+        mFinishedCallback = finishedCallback;
+        mApps = apps;
+        if (mRemoteCallback == null) mRemoteCallback = new RemoteAnimationFinishedStub(this);
         mWaitingAnimation = false;
         if (shouldMonitorCUJ(apps)) {
             interactionJankMonitor.begin(
@@ -125,7 +155,7 @@
         }
         try {
             getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
-                    nonApps, callback);
+                    nonApps, mRemoteCallback);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed call onAnimationStart", e);
         }
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 03a851b..4c25889 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
@@ -24,6 +24,7 @@
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.pm.LauncherApps;
+import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.UserManager;
 import android.view.Choreographer;
@@ -266,7 +267,8 @@
             AppHandleEducationController appHandleEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
-            FocusTransitionObserver focusTransitionObserver) {
+            FocusTransitionObserver focusTransitionObserver,
+            DesktopModeEventLogger desktopModeEventLogger) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return new DesktopModeWindowDecorViewModel(
                     context,
@@ -294,7 +296,8 @@
                     appHandleEducationController,
                     windowDecorCaptionHandleRepository,
                     desktopActivityOrientationHandler,
-                    focusTransitionObserver);
+                    focusTransitionObserver,
+                    desktopModeEventLogger);
         }
         return new CaptionWindowDecorViewModel(
                 context,
@@ -644,7 +647,10 @@
             @ShellMainThread Handler mainHandler,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             Optional<RecentTasksController> recentTasksController,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            InputManager inputManager,
+            FocusTransitionObserver focusTransitionObserver,
+            DesktopModeEventLogger desktopModeEventLogger) {
         return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
                 displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                 dragAndDropController, transitions, keyguardManager,
@@ -655,7 +661,9 @@
                 desktopRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
-                recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
+                recentTasksController.orElse(null), interactionJankMonitor, mainHandler,
+                inputManager, focusTransitionObserver,
+                desktopModeEventLogger);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
index 320c003..9d4926b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
@@ -71,7 +71,7 @@
 
     /** Whether there is an immersive transition that hasn't completed yet. */
     private val inProgress: Boolean
-        get() = state != null
+        get() = state != null || pendingExternalExitTransitions.isNotEmpty()
 
     private val rectEvaluator = RectEvaluator()
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 4350199..df9fc59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -58,9 +58,9 @@
         freeformTaskTransitionHandler.startMinimizedModeTransition(wct)
 
     /** Starts close transition and handles or delegates desktop task close animation. */
-    override fun startRemoveTransition(wct: WindowContainerTransaction?) {
+    override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder {
         requireNotNull(wct)
-        transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
+        return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
     }
 
     /** Returns null, as it only handles transitions started from Shell. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 8ebe503..255ca6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,11 +16,20 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.app.ActivityManager.RunningTaskInfo
+import android.util.Size
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.MotionEvent
+import android.view.MotionEvent.TOOL_TYPE_FINGER
+import android.view.MotionEvent.TOOL_TYPE_MOUSE
+import android.view.MotionEvent.TOOL_TYPE_STYLUS
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.protolog.ProtoLog
 import com.android.internal.util.FrameworkStatsLog
 import com.android.window.flags.Flags
 import com.android.wm.shell.EventLogTags
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import java.security.SecureRandom
 import java.util.Random
@@ -176,7 +185,13 @@
      * Logs that a task resize event is starting with [taskSizeUpdate] within a Desktop mode
      * session.
      */
-    fun logTaskResizingStarted(taskSizeUpdate: TaskSizeUpdate) {
+    fun logTaskResizingStarted(
+        resizeTrigger: ResizeTrigger,
+        motionEvent: MotionEvent?,
+        taskInfo: RunningTaskInfo,
+        displayController: DisplayController? = null,
+        displayLayoutSize: Size? = null,
+    ) {
         if (!Flags.enableResizingMetrics()) return
 
         val sessionId = currentSessionId.get()
@@ -188,11 +203,19 @@
             return
         }
 
+        val taskSizeUpdate = createTaskSizeUpdate(
+            resizeTrigger,
+            motionEvent,
+            taskInfo,
+            displayController = displayController,
+            displayLayoutSize = displayLayoutSize,
+        )
+
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
-            "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s",
+            "DesktopModeLogger: Logging task resize is starting, session: %s, taskSizeUpdate: %s",
             sessionId,
-            taskSizeUpdate.instanceId
+            taskSizeUpdate
         )
         logTaskSizeUpdated(
             FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
@@ -203,7 +226,15 @@
     /**
      * Logs that a task resize event is ending with [taskSizeUpdate] within a Desktop mode session.
      */
-    fun logTaskResizingEnded(taskSizeUpdate: TaskSizeUpdate) {
+    fun logTaskResizingEnded(
+        resizeTrigger: ResizeTrigger,
+        motionEvent: MotionEvent?,
+        taskInfo: RunningTaskInfo,
+        taskHeight: Int? = null,
+        taskWidth: Int? = null,
+        displayController: DisplayController? = null,
+        displayLayoutSize: Size? = null,
+    ) {
         if (!Flags.enableResizingMetrics()) return
 
         val sessionId = currentSessionId.get()
@@ -215,18 +246,61 @@
             return
         }
 
+        val taskSizeUpdate = createTaskSizeUpdate(
+            resizeTrigger,
+            motionEvent,
+            taskInfo,
+            taskHeight,
+            taskWidth,
+            displayController,
+            displayLayoutSize,
+        )
+
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
-            "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s",
+            "DesktopModeLogger: Logging task resize is ending, session: %s, taskSizeUpdate: %s",
             sessionId,
-            taskSizeUpdate.instanceId
+            taskSizeUpdate
         )
+
         logTaskSizeUpdated(
             FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
             sessionId, taskSizeUpdate
         )
     }
 
+    private fun createTaskSizeUpdate(
+        resizeTrigger: ResizeTrigger,
+        motionEvent: MotionEvent?,
+        taskInfo: RunningTaskInfo,
+        taskHeight: Int? = null,
+        taskWidth: Int? = null,
+        displayController: DisplayController? = null,
+        displayLayoutSize: Size? = null,
+    ): TaskSizeUpdate {
+        val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+
+        val height = taskHeight ?: taskBounds.height()
+        val width = taskWidth ?: taskBounds.width()
+
+        val displaySize = when {
+            displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width
+            displayController != null -> displayController.getDisplayLayout(taskInfo.displayId)
+                ?.let { it.height() * it.width() }
+            else -> null
+        }
+
+        return TaskSizeUpdate(
+            resizeTrigger,
+            getInputMethodFromMotionEvent(motionEvent),
+            taskInfo.taskId,
+            taskInfo.effectiveUid,
+            height,
+            width,
+            displaySize,
+        )
+    }
+
     fun logTaskInfoStateInit() {
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD,
@@ -238,7 +312,8 @@
                 taskHeight = 0,
                 taskWidth = 0,
                 taskX = 0,
-                taskY = 0)
+                taskY = 0
+            )
         )
     }
 
@@ -314,7 +389,7 @@
             /* task_width */
             taskSizeUpdate.taskWidth,
             /* display_area */
-            taskSizeUpdate.displayArea
+            taskSizeUpdate.displayArea ?: -1
         )
     }
 
@@ -364,9 +439,24 @@
             val uid: Int,
             val taskHeight: Int,
             val taskWidth: Int,
-            val displayArea: Int,
+            val displayArea: Int?,
         )
 
+        private fun getInputMethodFromMotionEvent(e: MotionEvent?): InputMethod {
+            if (e == null) return InputMethod.UNKNOWN_INPUT_METHOD
+
+            val toolType = e.getToolType(
+                e.findPointerIndex(e.getPointerId(0))
+            )
+            return when {
+                toolType == TOOL_TYPE_STYLUS -> InputMethod.STYLUS
+                toolType == TOOL_TYPE_MOUSE -> InputMethod.MOUSE
+                toolType == TOOL_TYPE_FINGER && e.source == SOURCE_MOUSE -> InputMethod.TOUCHPAD
+                toolType == TOOL_TYPE_FINGER && e.source == SOURCE_TOUCHSCREEN -> InputMethod.TOUCH
+                else -> InputMethod.UNKNOWN_INPUT_METHOD
+            }
+        }
+
         // Default value used when the task was not minimized.
         @VisibleForTesting
         const val UNSET_MINIMIZE_REASON =
@@ -499,6 +589,10 @@
                 FrameworkStatsLog
                     .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER
             ),
+            MAXIMIZE_MENU(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_MENU_RESIZE_TRIGGER
+            ),
         }
 
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index eeb7ac8..85a3126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -30,7 +30,6 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopTask
 import com.android.wm.shell.desktopmode.persistence.DesktopTaskState
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -124,7 +123,8 @@
         if (!Flags.enableDesktopWindowingPersistence()) return
         //  TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
         mainCoroutineScope.launch {
-            val desktop = persistentRepository.readDesktop()
+            val desktop = persistentRepository.readDesktop() ?: return@launch
+
             val maxTasks =
                 DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
                     ?: desktop.zOrderedTasksCount
@@ -132,13 +132,11 @@
             desktop.zOrderedTasksList
                 // Reverse it so we initialize the repo from bottom to top.
                 .reversed()
-                .map { taskId ->
-                    desktop.tasksByTaskIdMap.getOrDefault(
-                        taskId,
-                        DesktopTask.getDefaultInstance()
-                    )
+                .mapNotNull { taskId ->
+                    desktop.tasksByTaskIdMap[taskId]?.takeIf {
+                        it.desktopTaskState == DesktopTaskState.VISIBLE
+                    }
                 }
-                .filter { task -> task.desktopTaskState == DesktopTaskState.VISIBLE }
                 .take(maxTasks)
                 .forEach { task ->
                     addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
@@ -522,6 +520,7 @@
                 "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
             )
             pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
+            pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 29e302a..69776cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -35,6 +35,9 @@
 import android.graphics.PointF
 import android.graphics.Rect
 import android.graphics.Region
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
 import android.os.Binder
 import android.os.Handler
 import android.os.IBinder
@@ -42,6 +45,8 @@
 import android.util.Size
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.DragEvent
+import android.view.KeyEvent
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_CLOSE
@@ -57,6 +62,7 @@
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
+import com.android.hardware.input.Flags.useKeyGestureEventHandler
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -65,6 +71,7 @@
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags
+import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
@@ -78,12 +85,13 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
 import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.recents.RecentTasksController
 import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -92,7 +100,6 @@
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.annotations.ExternalThread
 import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
@@ -105,6 +112,7 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.sysui.UserChangeListener
+import com.android.wm.shell.transition.FocusTransitionObserver
 import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
@@ -118,7 +126,7 @@
 import java.util.Optional
 import java.util.concurrent.Executor
 import java.util.function.Consumer
-
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 /** Handles moving tasks in and out of desktop */
 class DesktopTasksController(
     private val context: Context,
@@ -149,11 +157,15 @@
     private val recentTasksController: RecentTasksController?,
     private val interactionJankMonitor: InteractionJankMonitor,
     @ShellMainThread private val handler: Handler,
+    private val inputManager: InputManager,
+    private val focusTransitionObserver: FocusTransitionObserver,
+    private val desktopModeEventLogger: DesktopModeEventLogger,
 ) :
     RemoteCallable<DesktopTasksController>,
     Transitions.TransitionHandler,
     DragAndDropController.DragAndDropListener,
-    UserChangeListener {
+    UserChangeListener,
+    KeyGestureEventHandler {
 
     private val desktopMode: DesktopModeImpl
     private var visualIndicator: DesktopModeVisualIndicator? = null
@@ -226,6 +238,9 @@
             }
         )
         dragAndDropController.addListener(this)
+        if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) {
+            inputManager.registerKeyGestureEventHandler(this)
+        }
     }
 
     @VisibleForTesting
@@ -409,7 +424,7 @@
         interactionJankMonitor.begin(taskSurface, context, handler,
             CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         dragToDesktopTransitionHandler.startDragToDesktopTransition(
-            taskInfo,
+            taskInfo.taskId,
             dragToDesktopValueAnimator
         )
     }
@@ -461,7 +476,12 @@
      * @param displayId display id of the window that's being closed
      * @param taskId task id of the window that's being closed
      */
-    fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) {
+    fun onDesktopWindowClose(
+        wct: WindowContainerTransaction,
+        displayId: Int,
+        taskInfo: RunningTaskInfo,
+    ): ((IBinder) -> Unit)? {
+        val taskId = taskInfo.taskId
         if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
             removeWallpaperActivity(wct)
         }
@@ -472,6 +492,7 @@
                 taskId
             )
         )
+        return immersiveTransitionHandler.exitImmersiveIfApplicable(wct, taskInfo)
     }
 
     fun minimizeTask(taskInfo: RunningTaskInfo) {
@@ -728,7 +749,11 @@
      * bounds) and a free floating state (either the last saved bounds if available or the default
      * bounds otherwise).
      */
-    fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
+    fun toggleDesktopTaskSize(
+        taskInfo: RunningTaskInfo,
+        resizeTrigger: ResizeTrigger,
+        motionEvent: MotionEvent?,
+    ) {
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
 
         val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
@@ -775,7 +800,10 @@
 
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
-
+        desktopModeEventLogger.logTaskResizingEnded(
+            resizeTrigger, motionEvent, taskInfo, destinationBounds.height(),
+            destinationBounds.width(), displayController
+        )
         toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
     }
 
@@ -865,9 +893,19 @@
         taskInfo: RunningTaskInfo,
         taskSurface: SurfaceControl,
         currentDragBounds: Rect,
-        position: SnapPosition
+        position: SnapPosition,
+        resizeTrigger: ResizeTrigger,
+        motionEvent: MotionEvent?,
     ) {
         val destinationBounds = getSnapBounds(taskInfo, position)
+        desktopModeEventLogger.logTaskResizingEnded(
+            resizeTrigger,
+            motionEvent,
+            taskInfo,
+            destinationBounds.height(),
+            destinationBounds.width(),
+            displayController,
+        )
         if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) {
             // Handle the case where we attempt to snap resize when already snap resized: the task
             // position won't need to change but we want to animate the surface going back to the
@@ -896,7 +934,8 @@
         position: SnapPosition,
         taskSurface: SurfaceControl,
         currentDragBounds: Rect,
-        dragStartBounds: Rect
+        dragStartBounds: Rect,
+        motionEvent: MotionEvent,
     ) {
         releaseVisualIndicator()
         if (!taskInfo.isResizeable && DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue()) {
@@ -913,10 +952,25 @@
                 isResizable = taskInfo.isResizeable,
             )
         } else {
+            val resizeTrigger = if (position == SnapPosition.LEFT) {
+                ResizeTrigger.DRAG_LEFT
+            } else {
+                ResizeTrigger.DRAG_RIGHT
+            }
+            desktopModeEventLogger.logTaskResizingStarted(
+                resizeTrigger, motionEvent, taskInfo, displayController
+            )
             interactionJankMonitor.begin(
                 taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
             )
-            snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
+            snapToHalfScreen(
+                taskInfo,
+                taskSurface,
+                currentDragBounds,
+                position,
+                resizeTrigger,
+                motionEvent,
+            )
         }
     }
 
@@ -1581,12 +1635,26 @@
         getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
     }
 
+    /** Move the focused desktop task in given `displayId` to next display. */
+    fun moveFocusedTaskToNextDisplay(displayId: Int) {
+        getFocusedFreeformTask(displayId)?.let { moveToNextDisplay(it.taskId) }
+    }
+
     private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
         return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo ->
             taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
         }
     }
 
+    // TODO(b/364154795): wait for the completion of moveToNextDisplay transition, otherwise it will
+    //  pick a wrong task when a user quickly perform other actions with keyboard shortcuts after
+    //  moveToNextDisplay.
+    private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
+        shellTaskOrganizer.getRunningTasks().find { taskInfo ->
+            taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+                    focusTransitionObserver.hasGlobalFocus(taskInfo)
+        }
+
     /**
      * Requests a task be transitioned from desktop to split select. Applies needed windowing
      * changes if this transition is enabled.
@@ -1702,6 +1770,7 @@
         currentDragBounds: Rect,
         validDragArea: Rect,
         dragStartBounds: Rect,
+        motionEvent: MotionEvent,
     ) {
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
@@ -1722,12 +1791,22 @@
             }
             IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
                 handleSnapResizingTask(
-                    taskInfo, SnapPosition.LEFT, taskSurface, currentDragBounds, dragStartBounds
+                    taskInfo,
+                    SnapPosition.LEFT,
+                    taskSurface,
+                    currentDragBounds,
+                    dragStartBounds,
+                    motionEvent,
                 )
             }
             IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
                 handleSnapResizingTask(
-                    taskInfo, SnapPosition.RIGHT, taskSurface, currentDragBounds, dragStartBounds
+                    taskInfo,
+                    SnapPosition.RIGHT,
+                    taskSurface,
+                    currentDragBounds,
+                    dragStartBounds,
+                    motionEvent,
                 )
             }
             IndicatorType.NO_INDICATOR -> {
@@ -1941,6 +2020,31 @@
         taskRepository.dump(pw, innerPrefix)
     }
 
+    override fun handleKeyGestureEvent(
+        event: KeyGestureEvent,
+        focusedToken: IBinder?
+    ): Boolean {
+        if (!isKeyGestureSupported(event.keyGestureType)) return false
+        when (event.keyGestureType) {
+            KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
+                if (event.keycodes.contains(KeyEvent.KEYCODE_D) &&
+                    event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)) {
+                    logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
+                    getGloballyFocusedFreeformTask()?.let { moveToNextDisplay(it.taskId) }
+                    return true
+                }
+                return false
+            }
+            else -> return false
+        }
+    }
+
+    override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
+        KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+            -> enableMoveToNextDisplayShortcut()
+        else -> false
+    }
+
     /** The interface for calls from outside the shell, within the host process. */
     @ExternalThread
     private inner class DesktopModeImpl : DesktopMode {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 34c2f1e..d7d5519 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -109,8 +109,8 @@
      * after one of the "end" or "cancel" transitions is merged into this transition.
      */
     fun startDragToDesktopTransition(
-        taskInfo: RunningTaskInfo,
-        dragToDesktopAnimator: MoveToDesktopAnimator
+        taskId: Int,
+        dragToDesktopAnimator: MoveToDesktopAnimator,
     ) {
         if (inProgress) {
             ProtoLog.v(
@@ -137,26 +137,23 @@
             )
         val wct = WindowContainerTransaction()
         wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
-        // The home launch done above will result in an attempt to move the task to pip if
-        // applicable, resulting in a broken state. Prevent that here.
-        wct.setDoNotPip(taskInfo.token)
         val startTransitionToken =
             transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
 
         transitionState =
-            if (isSplitTask(taskInfo.taskId)) {
+            if (isSplitTask(taskId)) {
                 val otherTask =
-                    getOtherSplitTask(taskInfo.taskId)
+                    getOtherSplitTask(taskId)
                         ?: throw IllegalStateException("Expected split task to have a counterpart.")
                 TransitionState.FromSplit(
-                    draggedTaskId = taskInfo.taskId,
+                    draggedTaskId = taskId,
                     dragAnimator = dragToDesktopAnimator,
                     startTransitionToken = startTransitionToken,
                     otherSplitTask = otherTask
                 )
             } else {
                 TransitionState.FromFullscreen(
-                    draggedTaskId = taskInfo.taskId,
+                    draggedTaskId = taskId,
                     dragAnimator = dragToDesktopAnimator,
                     startTransitionToken = startTransitionToken
                 )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
index 3f41d7c..2d11e02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -73,15 +73,14 @@
      */
     private suspend fun getDesktopRepositoryState(
         userId: Int = DEFAULT_USER_ID
-    ): DesktopRepositoryState =
+    ): DesktopRepositoryState? =
         try {
             dataStoreFlow
                 .first()
-                .desktopRepoByUserMap
-                .getOrDefault(userId, DesktopRepositoryState.getDefaultInstance())
+                .desktopRepoByUserMap[userId]
         } catch (e: Exception) {
             Log.e(TAG, "Unable to read from datastore", e)
-            DesktopRepositoryState.getDefaultInstance()
+            null
         }
 
     /**
@@ -91,13 +90,13 @@
     suspend fun readDesktop(
         userId: Int = DEFAULT_USER_ID,
         desktopId: Int = DEFAULT_DESKTOP_ID,
-    ): Desktop =
+    ): Desktop? =
         try {
             val repository = getDesktopRepositoryState(userId)
-            repository.getDesktopOrThrow(desktopId)
+            repository?.getDesktopOrThrow(desktopId)
         } catch (e: Exception) {
             Log.e(TAG, "Unable to get desktop info from persistent repository", e)
-            Desktop.getDefaultInstance()
+            null
         }
 
     /** Adds or updates a desktop stored in the datastore */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 6aaf001..58337ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -99,9 +99,11 @@
 
 
     @Override
-    public void startRemoveTransition(WindowContainerTransaction wct) {
+    public IBinder startRemoveTransition(WindowContainerTransaction wct) {
         final int type = WindowManager.TRANSIT_CLOSE;
-        mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
+        final IBinder transition = mTransitions.startTransition(type, wct, this);
+        mPendingTransitionTokens.add(transition);
+        return transition;
     }
 
     @Override
@@ -229,8 +231,7 @@
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         SurfaceControl sc = change.getLeash();
         finishT.hide(sc);
-        Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration
-                .getBounds());
+        final Rect startBounds = new Rect(change.getStartAbsBounds());
         animator.addUpdateListener(animation -> {
             t.setPosition(sc, startBounds.left,
                     startBounds.top + (animation.getAnimatedFraction() * screenHeight));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index ea68a69..5984d48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -48,6 +48,7 @@
      *
      * @param wct the {@link WindowContainerTransaction} that closes the task
      *
+     * @return the started transition
      */
-    void startRemoveTransition(WindowContainerTransaction wct);
+    IBinder startRemoveTransition(WindowContainerTransaction wct);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index b36b1f8..3e6d36c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -110,12 +110,12 @@
     void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
             @NonNull Executor executor);
 
-    /** Called when device waking up finished. */
-    void onFinishedWakingUp();
-
     /** Called when device starts going to sleep (screen off). */
     void onStartedGoingToSleep();
 
+    /** Called when device wakes up. */
+    void onStartedWakingUp();
+
     /** Called when requested to go to fullscreen from the current active split app. */
     void goToFullscreenFromSplit();
 
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 a23b576..6398d31 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
@@ -471,14 +471,14 @@
         mStageCoordinator.onKeyguardStateChanged(visible, occluded);
     }
 
-    public void onFinishedWakingUp() {
-        mStageCoordinator.onFinishedWakingUp();
-    }
-
     public void onStartedGoingToSleep() {
         mStageCoordinator.onStartedGoingToSleep();
     }
 
+    public void onStartedWakingUp() {
+        mStageCoordinator.onStartedWakingUp();
+    }
+
     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
     }
@@ -1084,13 +1084,13 @@
         }
 
         @Override
-        public void onFinishedWakingUp() {
-            mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+        public void onStartedGoingToSleep() {
+            mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
         }
 
         @Override
-        public void onStartedGoingToSleep() {
-            mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
+        public void onStartedWakingUp() {
+            mMainExecutor.execute(SplitScreenController.this::onStartedWakingUp);
         }
 
         @Override
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 3e76403..7893267 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
@@ -1138,14 +1138,10 @@
                 "onKeyguardVisibilityChanged: active=%b occludingTaskRunning=%b",
                 active, occludingTaskRunning);
         setDividerVisibility(!mKeyguardActive, null);
-
-        if (active && occludingTaskRunning) {
-            dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
-        }
     }
 
-    void onFinishedWakingUp() {
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
+    void onStartedWakingUp() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStartedWakingUp");
         if (mBreakOnNextWake) {
             dismissSplitKeepingLastActiveStage(EXIT_REASON_DEVICE_FOLDED);
         }
@@ -1908,60 +1904,6 @@
         }
     }
 
-    /** Callback when split roots have child or haven't under it.
-     * NOTICE: This only be called on legacy transition. */
-    @Override
-    public void onStageHasChildrenChanged(StageTaskListener stageListener) {
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
-                stageListener == mMainStage);
-        final boolean hasChildren = stageListener.mHasChildren;
-        final boolean isSideStage = stageListener == mSideStage;
-        if (!hasChildren && !mIsExiting && isSplitActive()) {
-            if (isSideStage && mMainStage.mVisible) {
-                // Exit to main stage if side stage no longer has children.
-                mSplitLayout.flingDividerToDismiss(
-                        mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                        EXIT_REASON_APP_FINISHED);
-            } else if (!isSideStage && mSideStage.mVisible) {
-                // Exit to side stage if main stage no longer has children.
-                mSplitLayout.flingDividerToDismiss(
-                        mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                        EXIT_REASON_APP_FINISHED);
-            } else if (!isSplitScreenVisible() && mSplitRequest == null) {
-                // Dismiss split screen in the background once any sides of the split become empty.
-                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
-            }
-        } else if (isSideStage && hasChildren && !isSplitActive()) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            prepareEnterSplitScreen(wct);
-
-            mSyncQueue.queue(wct);
-            mSyncQueue.runInSync(t -> {
-                if (mIsDropEntering) {
-                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-                    mIsDropEntering = false;
-                    mSkipEvictingMainStageChildren = false;
-                } else {
-                    mShowDecorImmediately = true;
-                    mSplitLayout.flingDividerToCenter(/*finishCallback*/ null);
-                }
-            });
-        }
-        if (mMainStage.mHasChildren && mSideStage.mHasChildren) {
-            mShouldUpdateRecents = true;
-            clearRequestIfPresented();
-            updateRecentTasksSplitPair();
-
-            if (!mLogger.hasStartedSession() && !mLogger.hasValidEnterSessionId()) {
-                mLogger.enterRequested(null /*enterSessionId*/, ENTER_REASON_MULTI_INSTANCE);
-            }
-            mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
-                    getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                    getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                    mSplitLayout.isLeftRightSplit());
-        }
-    }
-
     @Override
     public void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
             ActivityManager.RunningTaskInfo taskInfo) {
@@ -2485,6 +2427,10 @@
             final int transitType = info.getType();
             TransitionInfo.Change pipChange = null;
             int closingSplitTaskId = -1;
+            // This array tracks if we are sending stages TO_BACK in this transition.
+            // TODO (b/349828130): Update for n apps
+            boolean[] stagesSentToBack = new boolean[2];
+
             for (int iC = 0; iC < info.getChanges().size(); ++iC) {
                 final TransitionInfo.Change change = info.getChanges().get(iC);
                 if (change.getMode() == TRANSIT_CHANGE
@@ -2552,23 +2498,31 @@
                     }
                     continue;
                 }
+                final int taskId = taskInfo.taskId;
                 if (isOpeningType(change.getMode())) {
-                    if (!stage.containsTask(taskInfo.taskId)) {
+                    if (!stage.containsTask(taskId)) {
                         Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
-                                + " with " + taskInfo.taskId + " before startAnimation().");
-                        record.addRecord(stage, true, taskInfo.taskId);
+                                + " with " + taskId + " before startAnimation().");
+                        record.addRecord(stage, true, taskId);
                     }
                 } else if (change.getMode() == TRANSIT_CLOSE) {
-                    if (stage.containsTask(taskInfo.taskId)) {
-                        record.addRecord(stage, false, taskInfo.taskId);
+                    if (stage.containsTask(taskId)) {
+                        record.addRecord(stage, false, taskId);
                         Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
-                                + " with " + taskInfo.taskId + " before startAnimation().");
+                                + " with " + taskId + " before startAnimation().");
                     }
                 }
                 if (isClosingType(change.getMode()) &&
-                        getStageOfTask(change.getTaskInfo().taskId) != STAGE_TYPE_UNDEFINED) {
-                    // If either one of the 2 stages is closing we're assuming we'll break split
-                    closingSplitTaskId = change.getTaskInfo().taskId;
+                        getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED) {
+
+                    // Record which stages are getting sent to back
+                    if (change.getMode() == TRANSIT_TO_BACK) {
+                        stagesSentToBack[getStageOfTask(taskId)] = true;
+                    }
+
+                    // (For PiP transitions) If either one of the 2 stages is closing we're assuming
+                    // we'll break split
+                    closingSplitTaskId = taskId;
                 }
             }
 
@@ -2594,6 +2548,21 @@
                 return true;
             }
 
+            // If keyguard is active, check to see if we have our TO_BACK transitions in order.
+            // This array should either be all false (no split stages sent to back) or all true
+            // (all stages sent to back). In any other case (which can happen with SHOW_ABOVE_LOCKED
+            // apps) we should break split.
+            if (mKeyguardActive) {
+                boolean isFirstStageSentToBack = stagesSentToBack[0];
+                for (boolean b : stagesSentToBack) {
+                    // Compare each boolean to the first one. If any are different, break split.
+                    if (b != isFirstStageSentToBack) {
+                        dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+                        break;
+                    }
+                }
+            }
+
             final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
             if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
                     || dismissStages.size() == 1) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6313231..b33f3e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -75,8 +75,6 @@
     public interface StageListenerCallbacks {
         void onRootTaskAppeared();
 
-        void onStageHasChildrenChanged(StageTaskListener stageTaskListener);
-
         void onStageVisibilityChanged(StageTaskListener stageTaskListener);
 
         void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
@@ -207,7 +205,10 @@
                     mIconProvider);
             mHasRootTask = true;
             mCallbacks.onRootTaskAppeared();
-            sendStatusChanged();
+            if (mVisible != mRootTaskInfo.isVisible) {
+                mVisible = mRootTaskInfo.isVisible;
+                mCallbacks.onStageVisibilityChanged(this);
+            }
             mSyncQueue.runInSync(t -> mDimLayer =
                     SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer"));
         } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
@@ -498,22 +499,6 @@
         return true;
     }
 
-    private void sendStatusChanged() {
-        boolean hasChildren = mChildrenTaskInfo.size() > 0;
-        boolean visible = mRootTaskInfo.isVisible;
-        if (!mHasRootTask) return;
-
-        if (mHasChildren != hasChildren) {
-            mHasChildren = hasChildren;
-            mCallbacks.onStageHasChildrenChanged(this);
-        }
-
-        if (mVisible != visible) {
-            mVisible = visible;
-            mCallbacks.onStageVisibilityChanged(this);
-        }
-    }
-
     @Override
     @CallSuper
     public void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 509cb85..fde01ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -274,6 +274,7 @@
             closeDragResizeListener();
             mDragResizeListener = new DragResizeInputListener(
                     mContext,
+                    mTaskInfo,
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9e089b2..a775cbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -32,6 +32,7 @@
 
 import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
@@ -56,6 +57,7 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -102,6 +104,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -133,6 +136,7 @@
 
 import kotlin.Pair;
 import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
 
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
@@ -219,6 +223,7 @@
             };
     private final TaskPositionerFactory mTaskPositionerFactory;
     private final FocusTransitionObserver mFocusTransitionObserver;
+    private final DesktopModeEventLogger mDesktopModeEventLogger;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -246,7 +251,8 @@
             AppHandleEducationController appHandleEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
-            FocusTransitionObserver focusTransitionObserver) {
+            FocusTransitionObserver focusTransitionObserver,
+            DesktopModeEventLogger desktopModeEventLogger) {
         this(
                 context,
                 shellExecutor,
@@ -279,7 +285,8 @@
                 windowDecorCaptionHandleRepository,
                 activityOrientationChangeHandler,
                 new TaskPositionerFactory(),
-                focusTransitionObserver);
+                focusTransitionObserver,
+                desktopModeEventLogger);
     }
 
     @VisibleForTesting
@@ -315,7 +322,8 @@
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
             TaskPositionerFactory taskPositionerFactory,
-            FocusTransitionObserver focusTransitionObserver) {
+            FocusTransitionObserver focusTransitionObserver,
+            DesktopModeEventLogger desktopModeEventLogger) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -376,6 +384,7 @@
         };
         mTaskPositionerFactory = taskPositionerFactory;
         mFocusTransitionObserver = focusTransitionObserver;
+        mDesktopModeEventLogger = desktopModeEventLogger;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -545,15 +554,20 @@
                 >= MANAGE_WINDOWS_MINIMUM_INSTANCES);
     }
 
-    private void onMaximizeOrRestore(int taskId, String source) {
+    private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger,
+            MotionEvent motionEvent) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
         if (decoration == null) {
             return;
         }
+        mDesktopModeEventLogger.logTaskResizingStarted(resizeTrigger, motionEvent,
+                decoration.mTaskInfo,
+                mDisplayController, /* displayLayoutSize= */ null);
         mInteractionJankMonitor.begin(
                 decoration.mTaskSurface, mContext, mMainHandler,
                 Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
-        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
+        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger,
+                motionEvent);
         decoration.closeHandleMenu();
         decoration.closeMaximizeMenu();
     }
@@ -566,7 +580,7 @@
         mDesktopTasksController.toggleDesktopTaskFullImmersiveState(decoration.mTaskInfo);
     }
 
-    private void onSnapResize(int taskId, boolean left) {
+    private void onSnapResize(int taskId, boolean left, MotionEvent motionEvent) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
         if (decoration == null) {
             return;
@@ -577,13 +591,20 @@
             Toast.makeText(mContext,
                     R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
         } else {
+            ResizeTrigger resizeTrigger =
+                    left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU;
+            mDesktopModeEventLogger.logTaskResizingStarted(resizeTrigger, motionEvent,
+                    decoration.mTaskInfo,
+                    mDisplayController, /* displayLayoutSize= */ null);
             mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
                     Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
             mDesktopTasksController.snapToHalfScreen(
                     decoration.mTaskInfo,
                     decoration.mTaskSurface,
                     decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
-                    left ? SnapPosition.LEFT : SnapPosition.RIGHT);
+                    left ? SnapPosition.LEFT : SnapPosition.RIGHT,
+                    resizeTrigger,
+                    motionEvent);
         }
 
         decoration.closeHandleMenu();
@@ -735,6 +756,7 @@
         private boolean mTouchscreenInUse;
         private boolean mHasLongClicked;
         private int mDragPointerId = -1;
+        private MotionEvent mMotionEvent;
 
         private DesktopModeTouchEventListener(
                 RunningTaskInfo taskInfo,
@@ -767,8 +789,13 @@
                             SplitScreenController.EXIT_REASON_DESKTOP_MODE);
                 } else {
                     WindowContainerTransaction wct = new WindowContainerTransaction();
-                    mDesktopTasksController.onDesktopWindowClose(wct, mDisplayId, mTaskId);
-                    mTaskOperations.closeTask(mTaskToken, wct);
+                    final Function1<IBinder, Unit> runOnTransitionStart =
+                            mDesktopTasksController.onDesktopWindowClose(
+                                    wct, mDisplayId, decoration.mTaskInfo);
+                    final IBinder transition = mTaskOperations.closeTask(mTaskToken, wct);
+                    if (transition != null && runOnTransitionStart != null) {
+                        runOnTransitionStart.invoke(transition);
+                    }
                 }
             } else if (id == R.id.back_button) {
                 mTaskOperations.injectBackKey(mDisplayId);
@@ -791,7 +818,8 @@
                 } else {
                     // Full immersive is disabled or task doesn't request/support it, so just
                     // toggle between maximize/restore states.
-                    onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
+                    onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button",
+                            ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent);
                 }
             } else if (id == R.id.minimize_window) {
                 mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
@@ -800,6 +828,7 @@
 
         @Override
         public boolean onTouch(View v, MotionEvent e) {
+            mMotionEvent = e;
             final int id = v.getId();
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
@@ -890,6 +919,7 @@
          */
         @Override
         public boolean onGenericMotion(View v, MotionEvent ev) {
+            mMotionEvent = ev;
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final int id = v.getId();
             if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) {
@@ -1033,7 +1063,7 @@
                             taskInfo, decoration.mTaskSurface, position,
                             new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
                             newTaskBounds, decoration.calculateValidDragArea(),
-                            new Rect(mOnDragStartInitialBounds));
+                            new Rect(mOnDragStartInitialBounds), e);
                     if (touchingButton && !mHasLongClicked) {
                         // We need the input event to not be consumed here to end the ripple
                         // effect on the touched button. We will reset drag state in the ensuing
@@ -1080,7 +1110,7 @@
                 // Disallow double-tap to resize when in full immersive.
                 return false;
             }
-            onMaximizeOrRestore(mTaskId, "double_tap");
+            onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e);
             return true;
         }
     }
@@ -1477,7 +1507,8 @@
                         mGenericLinksParser,
                         mAssistContentRequester,
                         mMultiInstanceHelper,
-                        mWindowDecorCaptionHandleRepository);
+                        mWindowDecorCaptionHandleRepository,
+                        mDesktopModeEventLogger);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final TaskPositioner taskPositioner = mTaskPositionerFactory.create(
@@ -1494,15 +1525,16 @@
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
-            onMaximizeOrRestore(taskInfo.taskId, "maximize_menu");
+            onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU,
+                    touchEventListener.mMotionEvent);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnLeftSnapClickListener(() -> {
-            onSnapResize(taskInfo.taskId, true /* isLeft */);
+            onSnapResize(taskInfo.taskId, /* isLeft= */ true, touchEventListener.mMotionEvent);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnRightSnapClickListener(() -> {
-            onSnapResize(taskInfo.taskId, false /* isLeft */);
+            onSnapResize(taskInfo.taskId, /* isLeft= */ false, touchEventListener.mMotionEvent);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6eb20b9..d94f3a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -94,6 +94,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.CaptionState;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
@@ -216,7 +217,8 @@
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
-            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
+            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+            DesktopModeEventLogger desktopModeEventLogger) {
         this (context, userContext, displayController, splitScreenController, desktopRepository,
                 taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
                 appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
@@ -227,7 +229,7 @@
                 new SurfaceControlViewHostFactory() {},
                 DefaultMaximizeMenuFactory.INSTANCE,
                 DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
-                windowDecorCaptionHandleRepository);
+                windowDecorCaptionHandleRepository, desktopModeEventLogger);
     }
 
     DesktopModeWindowDecoration(
@@ -256,11 +258,12 @@
             MaximizeMenuFactory maximizeMenuFactory,
             HandleMenuFactory handleMenuFactory,
             MultiInstanceHelper multiInstanceHelper,
-            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
+            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+            DesktopModeEventLogger desktopModeEventLogger) {
         super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                 surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                 windowContainerTransactionSupplier, surfaceControlSupplier,
-                surfaceControlViewHostFactory);
+                surfaceControlViewHostFactory, desktopModeEventLogger);
         mSplitScreenController = splitScreenController;
         mHandler = handler;
         mBgExecutor = bgExecutor;
@@ -605,6 +608,7 @@
             Trace.beginSection("DesktopModeWindowDecoration#relayout-DragResizeInputListener");
             mDragResizeListener = new DragResizeInputListener(
                     mContext,
+                    mTaskInfo,
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
@@ -612,7 +616,8 @@
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
                     mSurfaceControlTransactionSupplier,
-                    mDisplayController);
+                    mDisplayController,
+                    mDesktopModeEventLogger);
             Trace.endSection();
         }
 
@@ -1700,7 +1705,8 @@
                 AppToWebGenericLinksParser genericLinksParser,
                 AssistContentRequester assistContentRequester,
                 MultiInstanceHelper multiInstanceHelper,
-                WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
+                WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+                DesktopModeEventLogger desktopModeEventLogger) {
             return new DesktopModeWindowDecoration(
                     context,
                     userContext,
@@ -1719,7 +1725,8 @@
                     genericLinksParser,
                     assistContentRequester,
                     multiInstanceHelper,
-                    windowDecorCaptionHandleRepository);
+                    windowDecorCaptionHandleRepository,
+                    desktopModeEventLogger);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 4ff394e..4204097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -29,10 +29,12 @@
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEventFromTouchscreen;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -59,6 +61,7 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -83,14 +86,17 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
 
     private final Context mContext;
+    private final RunningTaskInfo mTaskInfo;
     private final SurfaceControl mInputSinkSurface;
     private final IBinder mSinkClientToken;
     private final InputChannel mSinkInputChannel;
     private final DisplayController mDisplayController;
+    private final DesktopModeEventLogger mDesktopModeEventLogger;
     private final Region mTouchRegion = new Region();
 
     DragResizeInputListener(
             Context context,
+            RunningTaskInfo taskInfo,
             Handler handler,
             Choreographer choreographer,
             int displayId,
@@ -98,12 +104,15 @@
             DragPositioningCallback callback,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
-            DisplayController displayController) {
+            DisplayController displayController,
+            DesktopModeEventLogger desktopModeEventLogger) {
         mContext = context;
+        mTaskInfo = taskInfo;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mDisplayId = displayId;
         mDecorationSurface = decorationSurface;
         mDisplayController = displayController;
+        mDesktopModeEventLogger = desktopModeEventLogger;
         mClientToken = new Binder();
         final InputTransferToken inputTransferToken = new InputTransferToken();
         mInputChannel = new InputChannel();
@@ -125,11 +134,12 @@
             e.rethrowFromSystemServer();
         }
 
-        mInputEventReceiver = new TaskResizeInputEventReceiver(context, mInputChannel, callback,
+        mInputEventReceiver = new TaskResizeInputEventReceiver(context, mTaskInfo, mInputChannel,
+                callback,
                 handler, choreographer, () -> {
             final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
             return new Size(layout.width(), layout.height());
-        }, this::updateSinkInputChannel);
+        }, this::updateSinkInputChannel, mDesktopModeEventLogger);
         mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
 
         mInputSinkSurface = surfaceControlBuilderSupplier.get()
@@ -163,6 +173,22 @@
         }
     }
 
+    DragResizeInputListener(
+            Context context,
+            RunningTaskInfo taskInfo,
+            Handler handler,
+            Choreographer choreographer,
+            int displayId,
+            SurfaceControl decorationSurface,
+            DragPositioningCallback callback,
+            Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+            DisplayController displayController) {
+        this(context, taskInfo, handler, choreographer, displayId, decorationSurface, callback,
+                surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, displayController,
+                new DesktopModeEventLogger());
+    }
+
     /**
      * Updates the geometry (the touch region) of this drag resize handler.
      *
@@ -274,6 +300,7 @@
     private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
             DragDetector.MotionEventHandler {
         @NonNull private final Context mContext;
+        @NonNull private final RunningTaskInfo mTaskInfo;
         private final InputManager mInputManager;
         @NonNull private final InputChannel mInputChannel;
         @NonNull private final DragPositioningCallback mCallback;
@@ -282,6 +309,7 @@
         @NonNull private final DragDetector mDragDetector;
         @NonNull private final Supplier<Size> mDisplayLayoutSizeSupplier;
         @NonNull private final Consumer<Region> mTouchRegionConsumer;
+        @NonNull private final DesktopModeEventLogger mDesktopModeEventLogger;
         private final Rect mTmpRect = new Rect();
         private boolean mConsumeBatchEventScheduled;
         private DragResizeWindowGeometry mDragResizeWindowGeometry;
@@ -293,15 +321,24 @@
         // resize events. For example, if multiple fingers are touching the screen, then each one
         // has a separate pointer id, but we only accept drag input from one.
         private int mDragPointerId = -1;
+        // The type of resizing that is currently being done. Used to track the same resize trigger
+        // on start and end of the resizing action.
+        private ResizeTrigger mResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER;
+        // The last MotionEvent on ACTION_DOWN, used to track the input tool type and source for
+        // logging the start and end of the resizing action.
+        private MotionEvent mLastMotionEventOnDown;
 
         private TaskResizeInputEventReceiver(@NonNull Context context,
+                @NonNull RunningTaskInfo taskInfo,
                 @NonNull InputChannel inputChannel,
                 @NonNull DragPositioningCallback callback, @NonNull Handler handler,
                 @NonNull Choreographer choreographer,
                 @NonNull Supplier<Size> displayLayoutSizeSupplier,
-                @NonNull Consumer<Region> touchRegionConsumer) {
+                @NonNull Consumer<Region> touchRegionConsumer,
+                @NonNull DesktopModeEventLogger desktopModeEventLogger) {
             super(inputChannel, handler.getLooper());
             mContext = context;
+            mTaskInfo = taskInfo;
             mInputManager = context.getSystemService(InputManager.class);
             mInputChannel = inputChannel;
             mCallback = callback;
@@ -322,6 +359,7 @@
                     ViewConfiguration.get(mContext).getScaledTouchSlop());
             mDisplayLayoutSizeSupplier = displayLayoutSizeSupplier;
             mTouchRegionConsumer = touchRegionConsumer;
+            mDesktopModeEventLogger = desktopModeEventLogger;
         }
 
         /**
@@ -395,6 +433,7 @@
         @Override
         public boolean handleMotionEvent(View v, MotionEvent e) {
             boolean result = false;
+
             // Check if this is a touch event vs mouse event.
             // Touch events are tracked in four corners. Other events are tracked in resize edges.
             switch (e.getActionMasked()) {
@@ -416,6 +455,13 @@
                                 "%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
                         mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
                                 rawX, rawY);
+                        mLastMotionEventOnDown = e;
+                        mResizeTrigger = (ctrlType == CTRL_TYPE_BOTTOM || ctrlType == CTRL_TYPE_TOP
+                                || ctrlType == CTRL_TYPE_RIGHT || ctrlType == CTRL_TYPE_LEFT)
+                                ? ResizeTrigger.EDGE : ResizeTrigger.CORNER;
+                        mDesktopModeEventLogger.logTaskResizingStarted(mResizeTrigger,
+                                e, mTaskInfo, /* displayController= */ null,
+                                /* displayLayoutSize= */ mDisplayLayoutSizeSupplier.get());
                         // Increase the input sink region to cover the whole screen; this is to
                         // prevent input and focus from going to other tasks during a drag resize.
                         updateInputSinkRegionForDrag(mDragStartTaskBounds);
@@ -464,6 +510,12 @@
                         if (taskBounds.equals(mDragStartTaskBounds)) {
                             mTouchRegionConsumer.accept(mTouchRegion);
                         }
+
+                        mDesktopModeEventLogger.logTaskResizingEnded(mResizeTrigger,
+                                mLastMotionEventOnDown, mTaskInfo, taskBounds.height(),
+                                taskBounds.width(),
+                                /* displayController= */ null,
+                                /* displayLayoutSize= */ mDisplayLayoutSizeSupplier.get());
                     }
                     mShouldHandleEvents = false;
                     mDragPointerId = -1;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index 33d1c26..844ceb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -181,7 +181,7 @@
     }
 
     private boolean isInEdgeResizeBounds(float x, float y) {
-        return calculateEdgeResizeCtrlType(x, y) != 0;
+        return calculateEdgeResizeCtrlType(x, y) != CTRL_TYPE_UNDEFINED;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index 61b9393..bc85d2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -76,13 +76,14 @@
         closeTask(taskToken, new WindowContainerTransaction());
     }
 
-    void closeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) {
+    IBinder closeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) {
         wct.removeTask(taskToken);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mTransitionStarter.startRemoveTransition(wct);
+            return mTransitionStarter.startRemoveTransition(wct);
         } else {
             mSyncQueue.queue(wct);
         }
+        return null;
     }
 
     IBinder minimizeTask(WindowContainerToken taskToken) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6b3b357..34cc098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
@@ -111,6 +112,7 @@
     final Context mContext;
     final @NonNull Context mUserContext;
     final @NonNull DisplayController mDisplayController;
+    final @NonNull DesktopModeEventLogger mDesktopModeEventLogger;
     final ShellTaskOrganizer mTaskOrganizer;
     final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
     final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
@@ -163,7 +165,7 @@
         this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                 WindowContainerTransaction::new, SurfaceControl::new,
-                new SurfaceControlViewHostFactory() {});
+                new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger());
     }
 
     WindowDecoration(
@@ -177,13 +179,16 @@
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
             Supplier<SurfaceControl> surfaceControlSupplier,
-            SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+            SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+            @NonNull DesktopModeEventLogger desktopModeEventLogger
+    ) {
         mContext = context;
         mUserContext = userContext;
         mDisplayController = displayController;
         mTaskOrganizer = taskOrganizer;
         mTaskInfo = taskInfo;
         mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
+        mDesktopModeEventLogger = desktopModeEventLogger;
         mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index b7ddfd1..4fe66f3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -298,12 +298,18 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("MAXIMIZE_APP"),
                 extractor =
-                TaggedScenarioExtractorBuilder()
-                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
-                    .setTransitionMatcher(
-                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
-                    )
-                    .build(),
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            return transitions.filter {
+                                it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
+                            }
+                        }
+                    }
+                ),
                 assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
                         listOf(
                             AppLayerIncreasesInSize(DESKTOP_MODE_APP),
@@ -316,12 +322,18 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("MAXIMIZE_APP_NON_RESIZABLE"),
                 extractor =
-                TaggedScenarioExtractorBuilder()
-                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
-                    .setTransitionMatcher(
-                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
-                    )
-                    .build(),
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            return transitions.filter {
+                                it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
+                            }
+                        }
+                    }
+                ),
                 assertions =
                 AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
                         listOf(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index e6bd05b..f935ac7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -40,6 +40,8 @@
 
     private WindowContainerToken mToken = createMockWCToken();
     private int mParentTaskId = INVALID_TASK_ID;
+    private int mUid = INVALID_TASK_ID;
+    private int mTaskId = INVALID_TASK_ID;
     private Intent mBaseIntent = new Intent();
     private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
     private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -73,6 +75,18 @@
         return this;
     }
 
+    /** Sets the task info's effective UID. */
+    public TestRunningTaskInfoBuilder setUid(int uid) {
+        mUid = uid;
+        return this;
+    }
+
+    /** Sets the task info's UID. */
+    public TestRunningTaskInfoBuilder setTaskId(int taskId) {
+        mTaskId = taskId;
+        return this;
+    }
+
     /**
      * Set {@link ActivityManager.RunningTaskInfo#baseIntent} for the task info, by default
      * an empty intent is assigned
@@ -132,7 +146,8 @@
 
     public ActivityManager.RunningTaskInfo build() {
         final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
-        info.taskId = sNextTaskId++;
+        info.taskId = (mTaskId == INVALID_TASK_ID) ? sNextTaskId++ : mTaskId;
+        info.effectiveUid = mUid;
         info.baseIntent = mBaseIntent;
         info.parentTaskId = mParentTaskId;
         info.displayId = mDisplayId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
index b137468..ef99b00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
@@ -52,6 +52,7 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -366,7 +367,7 @@
             immersive = false
         )
 
-        immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)
+        immersiveHandler.exitImmersiveIfApplicable(wct, task)
 
         assertThat(wct.hasBoundsChange(task.token)).isFalse()
     }
@@ -384,12 +385,12 @@
             immersive = true
         )
 
-        immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition)
+        immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(transition)
 
         assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
             exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
                     && exit.taskId == task.taskId
-        }).isFalse()
+        }).isTrue()
     }
 
     @Test
@@ -405,7 +406,7 @@
             immersive = false
         )
 
-        immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition)
+        immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(transition)
 
         assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
             exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
@@ -565,6 +566,24 @@
         ).isTrue()
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    fun exitImmersive_pendingExit_doesNotExitAgain() {
+        val task = createFreeformTask()
+        whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+        val wct = WindowContainerTransaction()
+        desktopRepository.setTaskInFullImmersiveState(
+            displayId = task.displayId,
+            taskId = task.taskId,
+            immersive = true
+        )
+        immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(Binder())
+
+        immersiveHandler.moveTaskToNonImmersive(task)
+
+        verify(mockTransitions, never()).startTransition(any(), any(), any())
+    }
+
     private fun createTransitionInfo(
         @TransitionType type: Int = TRANSIT_CHANGE,
         @TransitionFlags flags: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 07de0716..81d59d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WindowingMode
+import android.os.Binder
 import android.os.Handler
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
@@ -107,6 +108,8 @@
     @Test
     fun startRemoveTransition_startsCloseTransition() {
         val wct = WindowContainerTransaction()
+        whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
+            .thenReturn(Binder())
 
         mixedHandler.startRemoveTransition(wct)
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 0825b6b..2a82e6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions
@@ -27,6 +30,9 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.EventLogTags
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
@@ -39,9 +45,13 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 /**
  * Tests for [DesktopModeEventLogger].
@@ -49,6 +59,8 @@
 class DesktopModeEventLoggerTest : ShellTestCase() {
 
     private val desktopModeEventLogger = DesktopModeEventLogger()
+    val displayController = mock<DisplayController>()
+    val displayLayout = mock<DisplayLayout>()
 
     @JvmField
     @Rule(order = 0)
@@ -60,6 +72,13 @@
     @Rule(order = 1)
     val setFlagsRule = SetFlagsRule()
 
+    @Before
+    fun setUp() {
+        doReturn(displayLayout).whenever(displayController).getDisplayLayout(anyInt())
+        doReturn(DISPLAY_WIDTH).whenever(displayLayout).width()
+        doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height()
+    }
+
     @Test
     fun logSessionEnter_logsEnterReasonWithNewSessionId() {
         desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
@@ -467,7 +486,8 @@
 
     @Test
     fun logTaskResizingStarted_noOngoingSession_doesNotLog() {
-        desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE)
+        desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER,
+            null, createTaskInfo())
 
         verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
         verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -478,13 +498,14 @@
     fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() {
         val sessionId = startDesktopModeSession()
 
-        desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE)
+        desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER,
+            null, createTaskInfo(), displayController)
 
         verify {
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
                 /* resize_trigger */
-                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER),
                 /* resizing_stage */
                 eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
                 /* input_method */
@@ -500,7 +521,7 @@
                 /* task_width */
                 eq(TASK_SIZE_UPDATE.taskWidth),
                 /* display_area */
-                eq(TASK_SIZE_UPDATE.displayArea),
+                eq(DISPLAY_AREA),
             )
         }
         verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -508,7 +529,8 @@
 
     @Test
     fun logTaskResizingEnded_noOngoingSession_doesNotLog() {
-        desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE)
+        desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER,
+            null, createTaskInfo())
 
         verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
         verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -519,13 +541,14 @@
     fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() {
         val sessionId = startDesktopModeSession()
 
-        desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE)
+        desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER,
+            null, createTaskInfo(), displayController = displayController)
 
         verify {
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
                 /* resize_trigger */
-                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER),
                 /* resizing_stage */
                 eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
                 /* input_method */
@@ -541,7 +564,7 @@
                 /* task_width */
                 eq(TASK_SIZE_UPDATE.taskWidth),
                 /* display_area */
-                eq(TASK_SIZE_UPDATE.displayArea),
+                eq(DISPLAY_AREA),
             )
         }
         verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -585,8 +608,14 @@
         }
     }
 
+    private fun createTaskInfo(): RunningTaskInfo {
+        return TestRunningTaskInfoBuilder().setTaskId(TASK_ID)
+            .setUid(TASK_UID)
+            .setBounds(Rect(TASK_X, TASK_Y, TASK_WIDTH, TASK_HEIGHT))
+            .build()
+    }
+
     private companion object {
-        private const val sessionId = 1
         private const val TASK_ID = 1
         private const val TASK_UID = 1
         private const val TASK_X = 0
@@ -594,7 +623,9 @@
         private const val TASK_HEIGHT = 100
         private const val TASK_WIDTH = 100
         private const val TASK_COUNT = 1
-        private const val DISPLAY_AREA = 1000
+        private const val DISPLAY_WIDTH = 500
+        private const val DISPLAY_HEIGHT = 500
+        private const val DISPLAY_AREA = DISPLAY_HEIGHT * DISPLAY_WIDTH
 
         private val TASK_UPDATE = TaskUpdate(
             TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 113990e..7c336cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -39,6 +39,9 @@
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
 import android.os.Binder
 import android.os.Bundle
 import android.os.Handler
@@ -50,6 +53,8 @@
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.DragEvent
 import android.view.Gravity
+import android.view.KeyEvent
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.WindowInsets
 import android.view.WindowManager
@@ -70,14 +75,18 @@
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
 import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
 import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
+import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
 import com.android.wm.shell.MockToken
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -91,6 +100,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -112,6 +122,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.FocusTransitionObserver
 import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.TestRemoteTransition
 import com.android.wm.shell.transition.Transitions
@@ -205,7 +216,11 @@
   @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
   @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
   @Mock private lateinit var mockHandler: Handler
+  @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
   @Mock lateinit var persistentRepository: DesktopPersistentRepository
+  @Mock private lateinit var mockInputManager: InputManager
+  @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
+  @Mock lateinit var motionEvent: MotionEvent
 
   private lateinit var mockitoSession: StaticMockitoSession
   private lateinit var controller: DesktopTasksController
@@ -214,6 +229,7 @@
   private lateinit var desktopTasksLimiter: DesktopTasksLimiter
   private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
   private lateinit var testScope: CoroutineScope
+  private lateinit var keyGestureEventHandler: KeyGestureEventHandler
 
   private val shellExecutor = TestShellExecutor()
 
@@ -271,6 +287,11 @@
     controller.setSplitScreenController(splitScreenController)
     controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
 
+    doAnswer {
+      keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
+      null
+    }.whenever(mockInputManager).registerKeyGestureEventHandler(any())
+
     shellInit.init()
 
     val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
@@ -278,6 +299,8 @@
     recentsTransitionStateListener = captor.value
 
     controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
   }
 
   private fun createController(): DesktopTasksController {
@@ -310,6 +333,9 @@
         recentTasksController,
         mockInteractionJankMonitor,
         mockHandler,
+        mockInputManager,
+        mockFocusTransitionObserver,
+        desktopModeEventLogger,
       )
   }
 
@@ -338,9 +364,17 @@
     val task1 = setUpFreeformTask()
 
     val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-    controller.toggleDesktopTaskSize(task1)
-    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+    controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
 
+    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task1,
+      STABLE_BOUNDS.height(),
+      STABLE_BOUNDS.width(),
+      displayController
+    )
     assertThat(argumentCaptor.value).isTrue()
   }
 
@@ -357,9 +391,17 @@
     val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
 
     val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-    controller.toggleDesktopTaskSize(task1)
-    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+    controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
 
+    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task1,
+      0,
+      0,
+      displayController
+    )
     assertThat(argumentCaptor.value).isFalse()
   }
 
@@ -735,7 +777,6 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
   fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     setUpLandscapeDisplay()
     val stableBounds = Rect()
     displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -754,7 +795,6 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
   fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     setUpLandscapeDisplay()
     val stableBounds = Rect()
     displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1467,6 +1507,44 @@
   }
 
   @Test
+  @EnableFlags(
+    FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
+    FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+    FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+  )
+  fun moveToNextDisplay_withKeyGesture() {
+    // Set up two display ids
+    whenever(rootTaskDisplayAreaOrganizer.displayIds)
+      .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+    // Create a mock for the target display area: default display
+    val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+    whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+      .thenReturn(defaultDisplayArea)
+    // Setup a focused task on secondary display, which is expected to move to default display
+    val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+    task.isFocused = true
+    whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
+    whenever(mockFocusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
+
+    val event = KeyGestureEvent.Builder()
+        .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
+        .setDisplayId(SECOND_DISPLAY)
+        .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
+        .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
+        .build()
+    val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+
+    assertThat(result).isTrue()
+    with(getLatestWct(type = TRANSIT_CHANGE)) {
+      assertThat(hierarchyOps).hasSize(1)
+      assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+      assertThat(hierarchyOps[0].isReparent).isTrue()
+      assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
+      assertThat(hierarchyOps[0].toTop).isTrue()
+    }
+  }
+
+  @Test
   fun getTaskWindowingMode() {
     val fullscreenTask = setUpFullscreenTask()
     val freeformTask = setUpFreeformTask()
@@ -1480,8 +1558,9 @@
 
   @Test
   fun onDesktopWindowClose_noActiveTasks() {
+    val task = setUpFreeformTask(active = false)
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = 1)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
     // Doesn't modify transaction
     assertThat(wct.hierarchyOps).isEmpty()
   }
@@ -1490,7 +1569,7 @@
   fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
     val task = setUpFreeformTask()
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
     // Doesn't modify transaction
     assertThat(wct.hierarchyOps).isEmpty()
   }
@@ -1502,7 +1581,7 @@
     taskRepository.wallpaperActivityToken = wallpaperToken
 
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
     // Adds remove wallpaper operation
     wct.assertRemoveAt(index = 0, wallpaperToken)
   }
@@ -1515,7 +1594,7 @@
     taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId)
 
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
     // Doesn't modify transaction
     assertThat(wct.hierarchyOps).isEmpty()
   }
@@ -1528,7 +1607,7 @@
     taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
 
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
     // Doesn't modify transaction
     assertThat(wct.hierarchyOps).isEmpty()
   }
@@ -1541,7 +1620,7 @@
     taskRepository.wallpaperActivityToken = wallpaperToken
 
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
     // Doesn't modify transaction
     assertThat(wct.hierarchyOps).isEmpty()
   }
@@ -1555,7 +1634,7 @@
     taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId)
 
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
     // Adds remove wallpaper operation
     wct.assertRemoveAt(index = 0, wallpaperToken)
   }
@@ -1569,7 +1648,7 @@
     taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
 
     val wct = WindowContainerTransaction()
-    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+    controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
     // Adds remove wallpaper operation
     wct.assertRemoveAt(index = 0, wallpaperToken)
   }
@@ -1715,8 +1794,6 @@
 
   @Test
   fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val homeTask = setUpHomeTask()
     val freeformTask = setUpFreeformTask()
     markTaskVisible(freeformTask)
@@ -1733,8 +1810,6 @@
 
   @Test
   fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val homeTask = setUpHomeTask()
     val freeformTask = setUpFreeformTask()
     markTaskVisible(freeformTask)
@@ -1760,8 +1835,6 @@
 
   @Test
   fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTask = setUpFreeformTask()
     markTaskVisible(freeformTask)
     val fullscreenTask = createFullscreenTask()
@@ -1775,8 +1848,6 @@
 
   @Test
   fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val fullscreenTask = createFullscreenTask()
@@ -1791,8 +1862,6 @@
 
   @Test
   fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val fullscreenTask = createFullscreenTask()
@@ -1808,8 +1877,6 @@
 
   @Test
   fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val minimizedTask = setUpFreeformTask()
     taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
     val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
@@ -1830,7 +1897,6 @@
 
   @Test
   fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
     tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -1847,7 +1913,6 @@
 
   @Test
   fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
     tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1860,8 +1925,6 @@
 
   @Test
   fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTask = setUpFreeformTask()
     markTaskHidden(freeformTask)
     val fullscreenTask = createFullscreenTask()
@@ -1870,16 +1933,12 @@
 
   @Test
   fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val fullscreenTask = createFullscreenTask()
     assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
   }
 
   @Test
   fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
     createFreeformTask(displayId = SECOND_DISPLAY)
 
@@ -1889,8 +1948,6 @@
 
   @Test
   fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val newFreeformTask = createFreeformTask()
@@ -1903,8 +1960,6 @@
 
   @Test
   fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTask = setUpFreeformTask()
     markTaskHidden(freeformTask)
 
@@ -1919,7 +1974,6 @@
 
   @Test
   fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
     tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -1934,7 +1988,6 @@
 
   @Test
   fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
     tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1951,8 +2004,6 @@
   @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTask1 = setUpFreeformTask()
     val freeformTask2 = createFreeformTask()
 
@@ -1968,8 +2019,6 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val freeformTask1 = setUpFreeformTask()
     val freeformTask2 = createFreeformTask()
 
@@ -1990,8 +2039,6 @@
   @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val task = createFreeformTask()
     val result = controller.handleRequest(Binder(), createTransition(task))
 
@@ -2003,8 +2050,6 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val task = createFreeformTask()
     val result = controller.handleRequest(Binder(), createTransition(task))
 
@@ -2019,8 +2064,6 @@
   @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
     // Second display task
     createFreeformTask(displayId = SECOND_DISPLAY)
@@ -2035,8 +2078,6 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
     // Second display task
     createFreeformTask(displayId = SECOND_DISPLAY)
@@ -2053,7 +2094,6 @@
 
   @Test
   fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false)
 
     val freeformTask1 = setUpFreeformTask()
@@ -2067,7 +2107,6 @@
 
   @Test
   fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true)
 
     val freeformTask1 = setUpFreeformTask()
@@ -2081,7 +2120,6 @@
 
   @Test
   fun handleRequest_freeformTask_keyguardLocked_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
     val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
 
@@ -2092,8 +2130,6 @@
 
   @Test
   fun handleRequest_notOpenOrToFrontTransition_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val task =
         TestRunningTaskInfoBuilder()
             .setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -2106,21 +2142,17 @@
 
   @Test
   fun handleRequest_noTriggerTask_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
   }
 
   @Test
   fun handleRequest_triggerTaskNotStandard_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
     assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
   }
 
   @Test
   fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
     val task =
         TestRunningTaskInfoBuilder()
             .setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -2802,7 +2834,8 @@
         PointF(200f, -200f), /* inputCoordinate */
         Rect(100, -100, 500, 1000), /* currentDragBounds */
         Rect(0, 50, 2000, 2000), /* validDragArea */
-        Rect() /* dragStartBounds */ )
+        Rect() /* dragStartBounds */,
+        motionEvent)
     val rectAfterEnd = Rect(100, 50, 500, 1150)
     verify(transitions)
         .startTransition(
@@ -2837,7 +2870,8 @@
       PointF(200f, 300f), /* inputCoordinate */
       currentDragBounds, /* currentDragBounds */
       Rect(0, 50, 2000, 2000) /* validDragArea */,
-      Rect() /* dragStartBounds */)
+      Rect() /* dragStartBounds */,
+      motionEvent)
 
 
     verify(transitions)
@@ -3084,10 +3118,19 @@
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
 
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task,
+      STABLE_BOUNDS.height(),
+      STABLE_BOUNDS.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3106,15 +3149,22 @@
       STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
     )
 
-    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
+    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, motionEvent)
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
     assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.SNAP_LEFT_MENU,
+      motionEvent,
+      task,
+      expectedBounds.height(),
+      expectedBounds.width(),
+      displayController
+    )
   }
 
   @Test
   fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
-    assumeTrue(ENABLE_SHELL_TRANSITIONS)
     // Set up task to already be in snapped-left bounds
     val bounds = Rect(
       STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
@@ -3129,7 +3179,7 @@
 
     // Attempt to snap left again
     val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
-    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
+    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, motionEvent)
 
     // Assert that task is NOT updated via WCT
     verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
@@ -3142,6 +3192,14 @@
       eq(bounds),
       eq(true)
     )
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.SNAP_LEFT_MENU,
+      motionEvent,
+      task,
+      bounds.height(),
+      bounds.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3152,12 +3210,22 @@
     }
     val preDragBounds = Rect(100, 100, 400, 500)
     val currentDragBounds = Rect(0, 100, 300, 500)
+    val expectedBounds =
+      Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom)
 
     controller.handleSnapResizingTask(
-      task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds)
+      task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent
+    )
     val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
     assertThat(findBoundsChange(wct, task)).isEqualTo(
-      Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom))
+      expectedBounds
+    )
+    verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
+      ResizeTrigger.DRAG_LEFT,
+      motionEvent,
+      task,
+      displayController
+    )
   }
 
   @Test
@@ -3170,7 +3238,7 @@
     val currentDragBounds = Rect(0, 100, 300, 500)
 
     controller.handleSnapResizingTask(
-      task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds)
+      task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent)
     verify(mReturnToDragStartAnimator).start(
       eq(task.taskId),
       eq(mockSurface),
@@ -3178,6 +3246,13 @@
       eq(preDragBounds),
       eq(false)
     )
+    verify(desktopModeEventLogger, never()).logTaskResizingStarted(
+      any(),
+      any(),
+      any(),
+      any(),
+      any()
+    )
   }
 
   @Test
@@ -3196,10 +3271,19 @@
     // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
     val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
 
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task,
+      expectedBounds.height(),
+      expectedBounds.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3207,8 +3291,12 @@
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
 
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
     assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
+    verify(desktopModeEventLogger, never()).logTaskResizingEnded(
+      any(), any(), any(), any(),
+      any(), any(), any()
+    )
   }
 
   @Test
@@ -3217,15 +3305,23 @@
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
 
     // Maximize
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
 
     // Restore
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task,
+      boundsBeforeMaximize.height(),
+      boundsBeforeMaximize.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3236,16 +3332,24 @@
     }
 
     // Maximize
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
       boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
 
     // Restore
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task,
+      boundsBeforeMaximize.height(),
+      boundsBeforeMaximize.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3256,16 +3360,24 @@
     }
 
     // Maximize
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
     task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
       STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
 
     // Restore
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task,
+      boundsBeforeMaximize.height(),
+      boundsBeforeMaximize.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3274,14 +3386,22 @@
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
 
     // Maximize
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
 
     // Restore
-    controller.toggleDesktopTaskSize(task)
+    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
 
     // Assert last bounds before maximize removed after use
     assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.MAXIMIZE_BUTTON,
+      motionEvent,
+      task,
+      boundsBeforeMaximize.height(),
+      boundsBeforeMaximize.width(),
+      displayController
+    )
   }
 
 
@@ -3680,14 +3800,11 @@
       handlerClass: Class<out TransitionHandler>? = null
   ): WindowContainerTransaction {
     val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-    if (ENABLE_SHELL_TRANSITIONS) {
-      if (handlerClass == null) {
-        verify(transitions).startTransition(eq(type), arg.capture(), isNull())
-      } else {
-        verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
-      }
+
+    if (handlerClass == null) {
+      verify(transitions).startTransition(eq(type), arg.capture(), isNull())
     } else {
-      verify(shellTaskOrganizer).applyTransaction(arg.capture())
+      verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
     }
     return arg.value
   }
@@ -3697,43 +3814,27 @@
   ): WindowContainerTransaction {
     val arg: ArgumentCaptor<WindowContainerTransaction> =
         ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+    verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
         .startTransition(capture(arg), eq(currentBounds))
-    } else {
-      verify(shellTaskOrganizer).applyTransaction(capture(arg))
-    }
     return arg.value
   }
 
   private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
     val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
-    } else {
-      verify(shellTaskOrganizer).applyTransaction(arg.capture())
-    }
+    verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
     return arg.value
   }
 
   private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
     val arg: ArgumentCaptor<WindowContainerTransaction> =
         ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
-    } else {
-      verify(shellTaskOrganizer).applyTransaction(capture(arg))
-    }
+    verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
     return arg.value
   }
 
   private fun getLatestExitDesktopWct(): WindowContainerTransaction {
     val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
-    } else {
-      verify(shellTaskOrganizer).applyTransaction(arg.capture())
-    }
+    verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
     return arg.value
   }
 
@@ -3741,27 +3842,15 @@
       wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
 
   private fun verifyWCTNotExecuted() {
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(transitions, never()).startTransition(anyInt(), any(), isNull())
-    } else {
-      verify(shellTaskOrganizer, never()).applyTransaction(any())
-    }
+    verify(transitions, never()).startTransition(anyInt(), any(), isNull())
   }
 
   private fun verifyExitDesktopWCTNotExecuted() {
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
-    } else {
-      verify(shellTaskOrganizer, never()).applyTransaction(any())
-    }
+    verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
   }
 
   private fun verifyEnterDesktopWCTNotExecuted() {
-    if (ENABLE_SHELL_TRANSITIONS) {
-      verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
-    } else {
-      verify(shellTaskOrganizer, never()).applyTransaction(any())
-    }
+    verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
   }
 
   private fun createTransition(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 0bd3e08..79e16fe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -607,7 +607,7 @@
                 )
             )
             .thenReturn(token)
-        handler.startDragToDesktopTransition(task, dragAnimator)
+        handler.startDragToDesktopTransition(task.taskId, dragAnimator)
         return token
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
index 9b9703f..8495580 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -115,8 +115,8 @@
                 freeformTasksInZOrder = freeformTasksInZOrder)
 
             val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
-            assertThat(actualDesktop.tasksByTaskIdMap).hasSize(2)
-            assertThat(actualDesktop.getZOrderedTasks(0)).isEqualTo(2)
+            assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2)
+            assertThat(actualDesktop?.getZOrderedTasks(0)).isEqualTo(2)
         }
     }
 
@@ -138,7 +138,7 @@
                 freeformTasksInZOrder = freeformTasksInZOrder)
 
             val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
-            assertThat(actualDesktop.tasksByTaskIdMap[task.taskId]?.desktopTaskState)
+            assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState)
                 .isEqualTo(DesktopTaskState.MINIMIZED)
         }
     }
@@ -161,8 +161,8 @@
                 freeformTasksInZOrder = freeformTasksInZOrder)
 
             val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
-            assertThat(actualDesktop.tasksByTaskIdMap).isEmpty()
-            assertThat(actualDesktop.zOrderedTasksList).isEmpty()
+            assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty()
+            assertThat(actualDesktop?.zOrderedTasksList).isEmpty()
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a6e33e5..a252a9d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -320,7 +320,7 @@
 
         assertEquals(mStageCoordinator.mLastActiveStage, STAGE_TYPE_MAIN);
 
-        mStageCoordinator.onFinishedWakingUp();
+        mStageCoordinator.onStartedWakingUp();
 
         verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 189684d..7144a1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -114,7 +114,6 @@
     public void testRootTaskAppeared() {
         assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);
         verify(mCallbacks).onRootTaskAppeared();
-        verify(mCallbacks, never()).onStageHasChildrenChanged(mStageTaskListener);
         verify(mCallbacks, never()).onStageVisibilityChanged(mStageTaskListener);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 175fbd2..1839b8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -87,6 +87,8 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -194,7 +196,11 @@
     @Mock private lateinit var mockAppHandleEducationController: AppHandleEducationController
     @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
     @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
+    @Mock private lateinit var motionEvent: MotionEvent
+    @Mock lateinit var displayController: DisplayController
+    @Mock lateinit var displayLayout: DisplayLayout
     private lateinit var spyContext: TestableContext
+    private lateinit var desktopModeEventLogger: DesktopModeEventLogger
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
         SurfaceControl.Transaction()
@@ -224,6 +230,7 @@
         shellInit = ShellInit(mockShellExecutor)
         windowDecorByTaskIdSpy.clear()
         spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
+        desktopModeEventLogger = mock<DesktopModeEventLogger>()
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
                 spyContext,
                 mockShellExecutor,
@@ -256,7 +263,8 @@
                 mockCaptionHandleRepository,
                 Optional.of(mockActivityOrientationChangeHandler),
                 mockTaskPositionerFactory,
-                mockFocusTransitionObserver
+                mockFocusTransitionObserver,
+                desktopModeEventLogger
         )
         desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -299,6 +307,10 @@
             argumentCaptor<DesktopModeKeyguardChangeListener>()
         verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
         desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
+        whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+        whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+        }
     }
 
     @After
@@ -612,7 +624,11 @@
 
         maxOrRestoreListenerCaptor.value.invoke()
 
-        verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
+        verify(mockDesktopTasksController).toggleDesktopTaskSize(
+            decor.mTaskInfo,
+            ResizeTrigger.MAXIMIZE_MENU,
+            null
+        )
     }
 
     @Test
@@ -647,7 +663,9 @@
             eq(decor.mTaskInfo),
             taskSurfaceCaptor.capture(),
             eq(currentBounds),
-            eq(SnapPosition.LEFT)
+            eq(SnapPosition.LEFT),
+            eq(ResizeTrigger.SNAP_LEFT_MENU),
+            eq(null)
         )
         assertEquals(taskSurfaceCaptor.firstValue, decor.mTaskSurface)
     }
@@ -685,7 +703,9 @@
             eq(decor.mTaskInfo),
             taskSurfaceCaptor.capture(),
             eq(currentBounds),
-            eq(SnapPosition.LEFT)
+            eq(SnapPosition.LEFT),
+            eq(ResizeTrigger.SNAP_LEFT_MENU),
+            eq(null)
         )
         assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
     }
@@ -704,7 +724,9 @@
         onLeftSnapClickListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController, never())
-            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT))
+            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
+                eq(ResizeTrigger.MAXIMIZE_BUTTON),
+                eq(null))
         verify(mockToast).show()
     }
 
@@ -725,7 +747,9 @@
             eq(decor.mTaskInfo),
             taskSurfaceCaptor.capture(),
             eq(currentBounds),
-            eq(SnapPosition.RIGHT)
+            eq(SnapPosition.RIGHT),
+            eq(ResizeTrigger.SNAP_RIGHT_MENU),
+            eq(null)
         )
         assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
     }
@@ -763,7 +787,9 @@
             eq(decor.mTaskInfo),
             taskSurfaceCaptor.capture(),
             eq(currentBounds),
-            eq(SnapPosition.RIGHT)
+            eq(SnapPosition.RIGHT),
+            eq(ResizeTrigger.SNAP_RIGHT_MENU),
+            eq(null)
         )
         assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
     }
@@ -782,7 +808,9 @@
         onRightSnapClickListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController, never())
-            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT))
+            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
+                eq(ResizeTrigger.MAXIMIZE_BUTTON),
+                eq(null))
         verify(mockToast).show()
     }
 
@@ -1247,7 +1275,7 @@
         onClickListenerCaptor.value.onClick(view)
 
         verify(mockDesktopTasksController)
-            .toggleDesktopTaskSize(decor.mTaskInfo)
+            .toggleDesktopTaskSize(decor.mTaskInfo, ResizeTrigger.MAXIMIZE_BUTTON, null)
     }
 
     private fun createOpenTaskDecoration(
@@ -1337,7 +1365,7 @@
         whenever(
             mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
-                any(), any(), any(), any(), any(), any(), any())
+                any(), any(), any(), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.user).thenReturn(mockUserHandle)
@@ -1378,5 +1406,6 @@
         private const val TAG = "DesktopModeWindowDecorViewModelTests"
         private val STABLE_INSETS = Rect(0, 100, 0, 0)
         private val INITIAL_BOUNDS = Rect(0, 0, 100, 100)
+        private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 3208872..0afb6c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,6 +106,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.CaptionState;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -210,6 +211,8 @@
     private MultiInstanceHelper mMockMultiInstanceHelper;
     @Mock
     private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository;
+    @Mock
+    private DesktopModeEventLogger mDesktopModeEventLogger;
     @Captor
     private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
     @Captor
@@ -1400,7 +1403,7 @@
                 mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
                 new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
                 maximizeMenuFactory, mMockHandleMenuFactory,
-                mMockMultiInstanceHelper, mMockCaptionHandleRepository);
+                mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger);
         windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
                 mMockTouchEventListener, mMockTouchEventListener);
         windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fb17ae9..cb7fade 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -83,6 +83,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.tests.R;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
@@ -138,6 +139,8 @@
     private SurfaceSyncGroup mMockSurfaceSyncGroup;
     @Mock
     private SurfaceControl mMockTaskSurface;
+    @Mock
+    private DesktopModeEventLogger mDesktopModeEventLogger;
 
     private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
             new ArrayList<>();
@@ -1014,7 +1017,7 @@
                 new MockObjectSupplier<>(mMockSurfaceControlTransactions,
                         () -> mock(SurfaceControl.Transaction.class)),
                 () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
-                mMockSurfaceControlViewHostFactory);
+                mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger);
     }
 
     private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1054,11 +1057,12 @@
                 Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
                 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
                 Supplier<SurfaceControl> surfaceControlSupplier,
-                SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+                SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+                DesktopModeEventLogger desktopModeEventLogger) {
             super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                     surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                     windowContainerTransactionSupplier, surfaceControlSupplier,
-                    surfaceControlViewHostFactory);
+                    surfaceControlViewHostFactory, desktopModeEventLogger);
         }
 
         @Override
diff --git a/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp b/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp
new file mode 100644
index 0000000..5905b32
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <math.h>
+
+#include "SkImageFilters.h"
+#include "TestSceneBase.h"
+#include "include/gpu/GpuTypes.h"  // from Skia
+#include "tests/common/BitmapAllocationTestUtils.h"
+#include "utils/Color.h"
+
+class WindowBlurKawase;
+
+static TestScene::Registrar _WindowBlurKawase(TestScene::Info{
+        "windowblurkawase", "Draws window Kawase blur",
+        TestScene::simpleCreateScene<WindowBlurKawase>});
+
+/**
+ * Simulates the multi-pass Kawase blur algorithm in
+ * frameworks/native/libs/renderengine/skia/filters/WindowBlurKawaseFilter.cpp
+ */
+class WindowBlurKawase : public TestScene {
+private:
+    // Keep in sync with
+    // frameworks/native/libs/renderengine/skia/filters/KawaseBlurFilter.h
+    static constexpr uint32_t kMaxPasses = 4;
+    // Keep in sync with frameworks/native/libs/renderengine/skia/filters/BlurFilter.h
+    static constexpr float kInputScale = 0.25f;
+
+    static constexpr uint32_t kLoopLength = 500;
+    static constexpr uint32_t kMaxBlurRadius = 300;
+    sk_sp<SkRuntimeEffect> mBlurEffect;
+
+    sp<RenderNode> card;
+    sp<RenderNode> contentNode;
+
+public:
+    explicit WindowBlurKawase() {
+        SkString blurString(
+                "uniform shader child;"
+                "uniform float in_blurOffset;"
+
+                "half4 main(float2 xy) {"
+                "half4 c = child.eval(xy);"
+                "c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));"
+                "c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));"
+                "c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));"
+                "c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));"
+                "return half4(c.rgb * 0.2, 1.0);"
+                "}");
+
+        auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
+        if (!blurEffect) {
+            LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
+        }
+        mBlurEffect = std::move(blurEffect);
+    }
+
+    void createContent(int width, int height, Canvas& canvas) override {
+        contentNode = TestUtils::createNode(
+                0, 0, width, height, [width, height](RenderProperties& props, Canvas& canvas) {
+                    canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+                    Paint paint;
+                    paint.setColor(Color::Red_500);
+                    canvas.drawRect(0, 0, width / 2, height / 2, paint);
+                    paint.setColor(Color::Blue_500);
+                    canvas.drawRect(width / 2, height / 2, width, height, paint);
+                });
+
+        card = TestUtils::createNode(
+                0, 0, width, height,
+                [this](RenderProperties& props, Canvas& canvas) { blurFrame(canvas, 0); });
+        canvas.drawRenderNode(card.get());
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % kLoopLength;
+        float blurRadius =
+                (sin((float)curFrame / kLoopLength * M_PI * 2) + 1) * 0.5 * kMaxBlurRadius;
+        TestUtils::recordNode(
+                *card, [this, blurRadius](Canvas& canvas) { blurFrame(canvas, blurRadius); });
+    }
+
+    void blurFrame(Canvas& canvas, float blurRadius) {
+        if (blurRadius == 0) {
+            canvas.drawRenderNode(contentNode.get());
+            return;
+        }
+
+        int width = canvas.width();
+        int height = canvas.height();
+        float tmpRadius = (float)blurRadius / 2.0f;
+        uint32_t numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+        float radiusByPasses = tmpRadius / (float)numberOfPasses;
+
+        SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+
+        sp<RenderNode> node = contentNode;
+        for (int i = 0; i < numberOfPasses; i++) {
+            blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale * (i + 1);
+            sk_sp<SkImageFilter> blurFilter =
+                    SkImageFilters::RuntimeShader(blurBuilder, radiusByPasses, "child", nullptr);
+            // Also downsample the image in the first pass.
+            float canvasScale = i == 0 ? kInputScale : 1;
+
+            // Apply the blur effect as an image filter.
+            node = TestUtils::createNode(
+                    0, 0, width * kInputScale, height * kInputScale,
+                    [node, blurFilter, canvasScale](RenderProperties& props, Canvas& canvas) {
+                        props.mutateLayerProperties().setImageFilter(blurFilter.get());
+                        canvas.scale(canvasScale, canvasScale);
+                        canvas.drawRenderNode(node.get());
+                    });
+        }
+
+        // Finally upsample the image to its original size.
+        canvas.scale(1 / kInputScale, 1 / kInputScale);
+        canvas.drawRenderNode(node.get());
+    }
+};
diff --git a/libs/hwui/tests/common/scenes/WindowBlurSkia.cpp b/libs/hwui/tests/common/scenes/WindowBlurSkia.cpp
new file mode 100644
index 0000000..36e6d8f
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/WindowBlurSkia.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <math.h>
+
+#include "SkImageFilters.h"
+#include "TestSceneBase.h"
+#include "include/gpu/GpuTypes.h"  // from Skia
+#include "tests/common/BitmapAllocationTestUtils.h"
+#include "utils/Color.h"
+
+class WindowBlurSkia;
+
+static TestScene::Registrar _WindowBlurSkia(TestScene::Info{
+        "windowblurskia", "Draws window Skia blur", TestScene::simpleCreateScene<WindowBlurSkia>});
+
+/**
+ * Simulates the Skia window blur in
+ * frameworks/native/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+ */
+class WindowBlurSkia : public TestScene {
+private:
+    // Keep in sync with frameworks/native/libs/renderengine/skia/filters/BlurFilter.h
+    static constexpr float kInputScale = 0.25f;
+
+    static constexpr uint32_t kLoopLength = 500;
+    static constexpr uint32_t kMaxBlurRadius = 300;
+
+    sp<RenderNode> card;
+    sp<RenderNode> contentNode;
+
+public:
+    void createContent(int width, int height, Canvas& canvas) override {
+        contentNode = TestUtils::createNode(
+                0, 0, width, height, [width, height](RenderProperties& props, Canvas& canvas) {
+                    canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+                    Paint paint;
+                    paint.setColor(Color::Red_500);
+                    canvas.drawRect(0, 0, width / 2, height / 2, paint);
+                    paint.setColor(Color::Blue_500);
+                    canvas.drawRect(width / 2, height / 2, width, height, paint);
+                });
+
+        card = TestUtils::createNode(
+                0, 0, width, height,
+                [this](RenderProperties& props, Canvas& canvas) { blurFrame(canvas, 0); });
+        canvas.drawRenderNode(card.get());
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % kLoopLength;
+        float blurRadius =
+                (sin((float)curFrame / kLoopLength * M_PI * 2) + 1) * 0.5 * kMaxBlurRadius;
+        TestUtils::recordNode(
+                *card, [this, blurRadius](Canvas& canvas) { blurFrame(canvas, blurRadius); });
+    }
+
+    void blurFrame(Canvas& canvas, float blurRadius) {
+        if (blurRadius == 0) {
+            canvas.drawRenderNode(contentNode.get());
+            return;
+        }
+
+        int width = canvas.width();
+        int height = canvas.height();
+
+        // Downsample and blur the image with the Skia blur filter.
+        sp<RenderNode> node = contentNode;
+        sk_sp<SkImageFilter> blurFilter =
+                SkImageFilters::Blur(blurRadius, blurRadius, SkTileMode::kClamp, nullptr, nullptr);
+        node = TestUtils::createNode(
+                0, 0, width * kInputScale, height * kInputScale,
+                [node, blurFilter](RenderProperties& props, Canvas& canvas) {
+                    props.mutateLayerProperties().setImageFilter(blurFilter.get());
+                    canvas.scale(kInputScale, kInputScale);
+                    canvas.drawRenderNode(node.get());
+                });
+
+        // Upsample the image to its original size.
+        canvas.scale(1 / kInputScale, 1 / kInputScale);
+        canvas.drawRenderNode(node.get());
+    }
+};
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 23097f6..c7a7ed2 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -17,7 +17,6 @@
 #include "Color.h"
 
 #include <Properties.h>
-#include <aidl/android/hardware/graphics/common/Dataspace.h>
 #include <android/hardware_buffer.h>
 #include <android/native_window.h>
 #include <ui/ColorSpace.h>
@@ -222,8 +221,7 @@
         if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) {
             return HAL_DATASPACE_BT2020;
         } else if (nearlyEqual(fn, SkNamedTransferFn::kSRGB)) {
-            return static_cast<android_dataspace>(
-                    ::aidl::android::hardware::graphics::common::Dataspace::DISPLAY_BT2020);
+            return static_cast<android_dataspace>(HAL_DATASPACE_DISPLAY_BT2020);
         }
     }
 
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index afa0a32..65e83b9 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -323,10 +323,15 @@
 
         int status;
         try {
-            status = mSoundTriggerSession.startRecognition(mSoundModel,
-                    mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
-                            allowMultipleTriggers, null, null, audioCapabilities),
-                    runInBatterySaver);
+            status = mSoundTriggerSession.startRecognition(
+                mSoundModel,
+                mRecognitionCallback,
+                new RecognitionConfig.Builder()
+                    .setCaptureRequested(captureTriggerAudio)
+                    .setAllowMultipleTriggers(allowMultipleTriggers)
+                    .setAudioCapabilities(audioCapabilities)
+                    .build(),
+                runInBatterySaver);
         } catch (RemoteException e) {
             return false;
         }
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 2e3ee32..e3f8fbb 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -20,6 +20,8 @@
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingTop="@dimen/settingslib_switchbar_margin"
     android:paddingBottom="@dimen/settingslib_switchbar_margin"
     android:orientation="vertical">
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
index 3e0e184..255b2c9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -20,6 +20,8 @@
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingTop="@dimen/settingslib_switchbar_margin"
     android:paddingBottom="@dimen/settingslib_switchbar_margin"
     android:orientation="vertical">
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml
new file mode 100644
index 0000000..94c6924
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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_height="wrap_content"
+    android:layout_width="match_parent"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:importantForAccessibility="no">
+
+    <com.android.settingslib.widget.MainSwitchBar
+        android:id="@+id/settingslib_main_switch_bar"
+        android:visibility="gone"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent" />
+
+</FrameLayout>
+
+
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 7c0eaea..bf34db9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -18,7 +18,11 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
-    android:layout_width="match_parent">
+    android:layout_width="match_parent"
+    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
     <TextView
         android:id="@+id/switch_text"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
index fa908a4..bef6e35 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
@@ -18,10 +18,6 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:importantForAccessibility="no">
 
     <com.android.settingslib.widget.MainSwitchBar
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index 3394874..83858d9 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -81,7 +81,11 @@
     }
 
     private void init(Context context, AttributeSet attrs) {
-        setLayoutResource(R.layout.settingslib_main_switch_layout);
+        boolean isExpressive = SettingsThemeHelper.isExpressiveTheme(context);
+        int resId = isExpressive
+                ? R.layout.settingslib_expressive_main_switch_layout
+                : R.layout.settingslib_main_switch_layout;
+        setLayoutResource(resId);
         mSwitchChangeListeners.add(this);
         if (attrs != null) {
             final TypedArray a = context.obtainStyledAttributes(attrs,
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index ad996c7..b64f5dc 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -38,3 +38,11 @@
     @StringRes override val title: Int = 0,
     @StringRes override val summary: Int = 0,
 ) : TwoStatePreference
+
+/** A preference that provides a two-state toggleable option that can be used as a main switch. */
+open class MainSwitchPreference
+@JvmOverloads
+constructor(
+    override val key: String,
+    @StringRes override val title: Int = 0,
+) : TwoStatePreference
\ No newline at end of file
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index bff95ce..fb06be9 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -32,6 +32,7 @@
     static_libs: [
         "SettingsLibDataStore",
         "SettingsLibMetadata",
+        "SettingsLibMainSwitchPreference",
         "androidx.annotation_annotation",
         "androidx.preference_preference",
         "guava",
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 4c2e1ba..43f2cb6 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.preference
 
+import com.android.settingslib.metadata.MainSwitchPreference
 import com.android.settingslib.metadata.PreferenceGroup
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.SwitchPreference
@@ -36,6 +37,7 @@
                 is SwitchPreference -> SwitchPreferenceBinding.INSTANCE
                 is PreferenceGroup -> PreferenceGroupBinding.INSTANCE
                 is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE
+                is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE
                 else -> DefaultPreferenceBinding
             }
 }
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index ede970e..d40a6f6 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -21,11 +21,13 @@
 import androidx.preference.PreferenceCategory
 import androidx.preference.PreferenceScreen
 import androidx.preference.SwitchPreferenceCompat
+import androidx.preference.TwoStatePreference
 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceScreenMetadata
 import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.widget.MainSwitchPreference
 
 /** Binding of preference group associated with [PreferenceCategory]. */
 interface PreferenceScreenBinding : PreferenceBinding {
@@ -64,23 +66,37 @@
     }
 }
 
-/** A boolean value type preference associated with [SwitchPreferenceCompat]. */
-interface SwitchPreferenceBinding : PreferenceBinding {
-
-    override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
+/** A boolean value type preference associated with the abstract [TwoStatePreference]. */
+interface TwoStatePreferenceBinding : PreferenceBinding {
 
     override fun bind(preference: Preference, metadata: PreferenceMetadata) {
         super.bind(preference, metadata)
         (metadata as? PersistentPreference<*>)
             ?.storage(preference.context)
             ?.getValue(metadata.key, Boolean::class.javaObjectType)
-            ?.let { (preference as SwitchPreferenceCompat).isChecked = it }
+            ?.let { (preference as TwoStatePreference).isChecked = it }
     }
+}
+
+/** A boolean value type preference associated with [SwitchPreferenceCompat]. */
+interface SwitchPreferenceBinding : TwoStatePreferenceBinding {
+
+    override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
 
     companion object {
         @JvmStatic val INSTANCE = object : SwitchPreferenceBinding {}
     }
 }
 
+/** A boolean value type preference associated with [MainSwitchPreference]. */
+interface MainSwitchPreferenceBinding : TwoStatePreferenceBinding {
+
+    override fun createWidget(context: Context): Preference = MainSwitchPreference(context)
+
+    companion object {
+        @JvmStatic val INSTANCE = object : MainSwitchPreferenceBinding {}
+    }
+}
+
 /** Default [PreferenceBinding] for [Preference]. */
 object DefaultPreferenceBinding : PreferenceBinding
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 4f315a2..63661f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -74,7 +74,23 @@
             new AudioDeviceCallback() {
                 @Override
                 public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
-                    applyDefaultSelectedTypeToAllPresets();
+                    // Activate the last hot plugged valid input device, to match the output device
+                    // behavior.
+                    @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType;
+                    for (AudioDeviceInfo info : addedDevices) {
+                        if (InputMediaDevice.isSupportedInputDevice(info.getType())) {
+                            deviceTypeToActivate = info.getType();
+                        }
+                    }
+
+                    // Only activate if we find a different valid input device. e.g. if none of the
+                    // addedDevices is supported input device, we don't need to activate anything.
+                    if (mSelectedInputDeviceType != deviceTypeToActivate) {
+                        mSelectedInputDeviceType = deviceTypeToActivate;
+                        AudioDeviceAttributes deviceAttributes =
+                                createInputDeviceAttributes(mSelectedInputDeviceType);
+                        setPreferredDeviceForAllPresets(deviceAttributes);
+                    }
                 }
 
                 @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index d3c345d..f5e6caf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -25,6 +25,7 @@
 import android.media.IVolumeController
 import android.provider.Settings
 import android.util.Log
+import android.view.KeyEvent
 import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
 import com.android.settingslib.volume.data.model.VolumeControllerEvent
@@ -104,6 +105,8 @@
     @AudioDeviceCategory suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
 
     suspend fun notifyVolumeControllerVisible(isVisible: Boolean)
+
+    fun dispatchMediaKeyEvent(event: KeyEvent)
 }
 
 class AudioRepositoryImpl(
@@ -265,6 +268,10 @@
         }
     }
 
+    override fun dispatchMediaKeyEvent(event: KeyEvent) {
+        audioManager.dispatchMediaKeyEvent(event)
+    }
+
     private fun getMinVolume(stream: AudioStream): Int =
         try {
             audioManager.getStreamMinVolume(stream.value)
@@ -320,15 +327,9 @@
         mutableEvents.tryEmit(VolumeControllerEvent.SetA11yMode(mode))
     }
 
-    override fun displayCsdWarning(
-        csdWarning: Int,
-        displayDurationMs: Int,
-    ) {
+    override fun displayCsdWarning(csdWarning: Int, displayDurationMs: Int) {
         mutableEvents.tryEmit(
-            VolumeControllerEvent.DisplayCsdWarning(
-                csdWarning,
-                displayDurationMs,
-            )
+            VolumeControllerEvent.DisplayCsdWarning(csdWarning, displayDurationMs)
         )
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 782cee2..d808a25 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -138,6 +139,18 @@
                 /* address= */ "");
     }
 
+    private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() {
+        return new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_INPUT,
+                AudioDeviceInfo.TYPE_USB_HEADSET,
+                /* address= */ "");
+    }
+
+    private AudioDeviceAttributes getHdmiDeviceAttributes() {
+        return new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ "");
+    }
+
     private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
         final List<AudioDeviceAttributes> audioDeviceAttributesList =
                 new ArrayList<AudioDeviceAttributes>();
@@ -303,21 +316,47 @@
     }
 
     @Test
-    public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
+    public void onAudioDevicesAdded_shouldActivateAddedDevice() {
         final AudioManager audioManager = mock(AudioManager.class);
-        AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
-        when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
-                .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
-
         InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
         AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
         inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
 
-        // Called twice, one after initiation, the other after onAudioDevicesAdded call.
-        verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+        // The only added wired headset will be activated.
         for (@MediaRecorder.Source int preset : PRESETS) {
-            verify(audioManager, atLeast(2))
-                    .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
+            verify(audioManager, atLeast(1))
+                    .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+        }
+    }
+
+    @Test
+    public void onAudioDevicesAdded_shouldActivateLastAddedDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+        AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()};
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // When adding multiple valid input devices, the last added device (usb headset in this
+        // case) will be activated.
+        for (@MediaRecorder.Source int preset : PRESETS) {
+            verify(audioManager, never())
+                    .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+            verify(audioManager, atLeast(1))
+                    .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes());
+        }
+    }
+
+    @Test
+    public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+        AudioDeviceInfo[] devices = {mockHdmiInfo()};
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // Do not activate since HDMI is not a valid input device.
+        for (@MediaRecorder.Source int preset : PRESETS) {
+            verify(audioManager, never())
+                    .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes());
         }
     }
 
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 65b2275..00ae05c 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -76,6 +76,7 @@
         "truth",
         "Nene",
         "Harrier",
+        "bedstead-enterprise",
     ],
     libs: [
         "android.test.base.stubs.system",
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e86e727..9cce431 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -20,6 +20,9 @@
 import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
 import static android.provider.Settings.System.RINGTONE;
 
+import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.workProfile;
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.pm.PackageManager;
@@ -82,7 +85,7 @@
     @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
     @EnsureHasWorkProfile
     public void testSettings_workProfile() throws Exception {
-        UserReference profile = sDeviceState.workProfile();
+        UserReference profile = workProfile(sDeviceState);
 
         // Settings.Global settings are shared between different users
         assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), profile.id());
@@ -96,7 +99,7 @@
     @RequireRunOnInitialUser
     @EnsureHasSecondaryUser
     public void testSettings_secondaryUser() throws Exception {
-        UserReference secondaryUser = sDeviceState.secondaryUser();
+        UserReference secondaryUser = secondaryUser(sDeviceState);
 
         // Settings.Global settings are shared between different users
         assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), secondaryUser.id());
@@ -223,7 +226,7 @@
     @RequireRunOnInitialUser
     @EnsureHasSecondaryUser
     public void testSettings_stopAndRestartSecondaryUser() throws Exception {
-        UserReference secondaryUser = sDeviceState.secondaryUser();
+        UserReference secondaryUser = secondaryUser(sDeviceState);
 
         assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3c560fd..5f90b39 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -276,8 +276,6 @@
 filegroup {
     name: "SystemUI-tests-broken-robofiles-compile",
     srcs: [
-        "tests/src/**/*DeviceOnlyTest.java",
-        "tests/src/**/*DeviceOnlyTest.kt",
         "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
         "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
         "tests/src/**/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt",
@@ -293,7 +291,6 @@
         "tests/src/**/systemui/keyguard/ResourceTrimmerTest.kt",
         "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
         "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
         "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
         "tests/src/**/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt",
         "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
@@ -303,14 +300,12 @@
         "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
         "tests/src/**/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt",
         "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
-        "tests/src/**/systemui/statusbar/commandline/CommandRegistryTest.kt",
         "tests/src/**/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt",
         "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
         "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
         "tests/src/**/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt",
         "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
-        "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
         "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
         "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt",
         "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
@@ -449,27 +444,20 @@
         "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
         "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
         "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
-        "tests/src/**/systemui/power/PowerUITest.java",
         "tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
         "tests/src/**/systemui/qs/QSImplTest.java",
-        "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
-        "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
         "tests/src/**/systemui/qs/tiles/QuickAccessWalletTileTest.java",
         "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
-        "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
-        "tests/src/**/systemui/statusbar/CommandQueueTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
         "tests/src/**/systemui/statusbar/connectivity/NetworkControllerBaseTest.java",
-        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
-        "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
-        "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
-        "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
-        "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
-        "tests/src/**/systemui/toast/ToastUITest.java",
         "tests/src/**/systemui/statusbar/connectivity/NetworkControllerDataTest.java",
         "tests/src/**/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java",
         "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
         "tests/src/**/systemui/statusbar/connectivity/NetworkControllerWifiTest.java",
+        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
+        "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
+        "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
+        "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
+        "tests/src/**/systemui/toast/ToastUITest.java",
     ],
     visibility: ["//visibility:private"],
 }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0b18110..1c29db1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1148,6 +1148,13 @@
 }
 
 flag {
+  name: "media_controls_button_media3_placement"
+  namespace: "systemui"
+  description: "Use media3 API for action button placement preferences"
+  bug: "360196209"
+}
+
+flag {
   name: "media_controls_drawables_reuse"
   namespace: "systemui"
   description: "Re-use created media drawables for media controls"
@@ -1552,6 +1559,16 @@
 }
 
 flag {
+   name: "hide_ringer_button_in_single_volume_mode"
+   namespace: "systemui"
+   description: "When the device is in single volume mode, hide the ringer button because it doesn't work"
+   bug: "374870615"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
     name: "qs_tile_detailed_view"
     namespace: "systemui"
     description: "Enables the tile detailed view UI."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 8b0daf6..476cced 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -159,6 +159,7 @@
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.viewinterop.NoOpUpdate
+import androidx.compose.ui.zIndex
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.window.layout.WindowMetricsCalculator
 import com.android.compose.animation.Easings.Emphasized
@@ -796,6 +797,7 @@
                 }
 
             if (viewModel.isEditMode && dragDropState != null) {
+                val isItemDragging = dragDropState.draggingItemKey == item.key
                 val outlineAlpha by
                     animateFloatAsState(
                         targetValue = if (selected) 1f else 0f,
@@ -812,13 +814,13 @@
                     enabled = selected,
                     alpha = { outlineAlpha },
                     modifier =
-                        Modifier.requiredSize(dpSize).thenIf(
-                            dragDropState.draggingItemKey != item.key
-                        ) {
-                            Modifier.animateItem(
-                                placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
-                            )
-                        },
+                        Modifier.requiredSize(dpSize)
+                            .thenIf(!isItemDragging) {
+                                Modifier.animateItem(
+                                    placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
+                                )
+                            }
+                            .thenIf(isItemDragging) { Modifier.zIndex(1f) },
                     onResize = { resizeInfo -> contentListState.resize(index, resizeInfo) },
                     minHeightPx = widgetSizeInfo.minHeightPx,
                     maxHeightPx = widgetSizeInfo.maxHeightPx,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 900971b..2a85823 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -39,7 +39,7 @@
     val resources: Resources,
     private val hasStepClockAnimation: Boolean = false,
     private val migratedClocks: Boolean = false,
-    private val clockReactiveVariants: Boolean = false,
+    private val isClockReactiveVariantsEnabled: Boolean = false,
 ) : ClockProvider {
     private var messageBuffers: ClockMessageBuffers? = null
 
@@ -54,7 +54,7 @@
             throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
         }
 
-        return if (clockReactiveVariants) {
+        return if (isClockReactiveVariantsEnabled) {
             val buffer =
                 messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
             val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
@@ -84,7 +84,7 @@
             // TODO(b/352049256): Update placeholder to actual resource
             resources.getDrawable(R.drawable.clock_default_thumbnail, null),
             isReactiveToTone = true,
-            isReactiveToTouch = clockReactiveVariants,
+            isReactiveToTouch = isClockReactiveVariantsEnabled,
             axes = listOf(), // TODO: Ater some picker definition
         )
     }
@@ -103,7 +103,6 @@
                                     timespec = DigitalTimespec.FIRST_DIGIT,
                                     style =
                                         FontTextStyle(
-                                            fontFamily = "google_sans_flex.ttf",
                                             lineHeight = 147.25f,
                                             fontVariation =
                                                 "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -112,7 +111,6 @@
                                         FontTextStyle(
                                             fontVariation =
                                                 "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
-                                            fontFamily = "google_sans_flex.ttf",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -131,7 +129,6 @@
                                     timespec = DigitalTimespec.SECOND_DIGIT,
                                     style =
                                         FontTextStyle(
-                                            fontFamily = "google_sans_flex.ttf",
                                             lineHeight = 147.25f,
                                             fontVariation =
                                                 "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -140,7 +137,6 @@
                                         FontTextStyle(
                                             fontVariation =
                                                 "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
-                                            fontFamily = "google_sans_flex.ttf",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -159,7 +155,6 @@
                                     timespec = DigitalTimespec.FIRST_DIGIT,
                                     style =
                                         FontTextStyle(
-                                            fontFamily = "google_sans_flex.ttf",
                                             lineHeight = 147.25f,
                                             fontVariation =
                                                 "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -168,7 +163,6 @@
                                         FontTextStyle(
                                             fontVariation =
                                                 "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
-                                            fontFamily = "google_sans_flex.ttf",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -187,7 +181,6 @@
                                     timespec = DigitalTimespec.SECOND_DIGIT,
                                     style =
                                         FontTextStyle(
-                                            fontFamily = "google_sans_flex.ttf",
                                             lineHeight = 147.25f,
                                             fontVariation =
                                                 "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -196,7 +189,6 @@
                                         FontTextStyle(
                                             fontVariation =
                                                 "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
-                                            fontFamily = "google_sans_flex.ttf",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -221,13 +213,11 @@
                         timespec = DigitalTimespec.TIME_FULL_FORMAT,
                         style =
                             FontTextStyle(
-                                fontFamily = "google_sans_flex.ttf",
                                 fontVariation = "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100",
                                 fontSizeScale = 0.98f,
                             ),
                         aodStyle =
                             FontTextStyle(
-                                fontFamily = "google_sans_flex.ttf",
                                 fontVariation = "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100",
                                 fillColorLight = "#FFFFFFFF",
                                 outlineColor = "#00000000",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index af3ddfc..29ee874 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
@@ -27,6 +30,7 @@
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.inputmethod.data.model.InputMethodModel
 import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
@@ -67,11 +71,12 @@
     private val inputMethodInteractor by lazy { kosmos.inputMethodInteractor }
     private val isInputEnabled = MutableStateFlow(true)
 
-    private val underTest =
+    private val underTest by lazy {
         kosmos.passwordBouncerViewModelFactory.create(
             isInputEnabled = isInputEnabled,
             onIntentionalUserInput = {},
         )
+    }
 
     @Before
     fun setUp() {
@@ -345,6 +350,37 @@
             assertThat(textInputFocusRequested).isFalse()
         }
 
+    @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
+    @Test
+    fun consumeConfirmKeyEvents_toPreventItFromPropagating() =
+        testScope.runTest { verifyConfirmKeyEventsBehavior(keyUpEventConsumed = true) }
+
+    @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
+    @EnableSceneContainer
+    @Test
+    fun noops_whenSceneContainerIsAlsoEnabled() =
+        testScope.runTest { verifyConfirmKeyEventsBehavior(keyUpEventConsumed = false) }
+
+    private fun verifyConfirmKeyEventsBehavior(keyUpEventConsumed: Boolean) {
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_DPAD_CENTER))
+            .isFalse()
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_DPAD_CENTER))
+            .isEqualTo(keyUpEventConsumed)
+
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_ENTER)).isFalse()
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_ENTER))
+            .isEqualTo(keyUpEventConsumed)
+
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_NUMPAD_ENTER))
+            .isFalse()
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_NUMPAD_ENTER))
+            .isEqualTo(keyUpEventConsumed)
+
+        // space is ignored.
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_SPACE)).isFalse()
+        assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_SPACE)).isFalse()
+    }
+
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt
index 855b6d0..587d3d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.VibratorHelper
@@ -141,11 +142,7 @@
         }
 
     private fun createPlugin() {
-        plugin =
-            SeekbarHapticPlugin(
-                vibratorHelper,
-                kosmos.fakeSystemClock,
-            )
+        plugin = SeekbarHapticPlugin(vibratorHelper, kosmos.msdlPlayer, kosmos.fakeSystemClock)
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 28f88fe..3467382 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -16,16 +16,26 @@
 
 package com.android.systemui.haptics.slider
 
+import android.os.VibrationAttributes
 import android.os.VibrationEffect
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.haptics.fakeVibratorHelper
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.common.truth.Truth.assertThat
 import kotlin.math.max
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -35,6 +45,7 @@
 class SliderHapticFeedbackProviderTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private val config = SliderHapticFeedbackConfig()
 
@@ -44,7 +55,14 @@
     private val dragTextureThresholdMillis =
         lowTickDuration * config.numberOfLowTicks + config.deltaMillisForDragInterval
     private val vibratorHelper = kosmos.fakeVibratorHelper
+    private val msdlPlayer = kosmos.fakeMSDLPlayer
     private lateinit var sliderHapticFeedbackProvider: SliderHapticFeedbackProvider
+    private val pipeliningAttributes =
+        VibrationAttributes.Builder()
+            .setUsage(VibrationAttributes.USAGE_TOUCH)
+            .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+            .build()
+    private lateinit var dynamicProperties: InteractionProperties.DynamicVibrationScale
 
     @Before
     fun setup() {
@@ -54,13 +72,20 @@
         sliderHapticFeedbackProvider =
             SliderHapticFeedbackProvider(
                 vibratorHelper,
+                msdlPlayer,
                 dragVelocityProvider,
                 config,
                 kosmos.fakeSystemClock,
             )
+        dynamicProperties =
+            InteractionProperties.DynamicVibrationScale(
+                sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+                pipeliningAttributes,
+            )
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtLowerBookend_playsClick() =
         with(kosmos) {
             val vibration =
@@ -77,6 +102,18 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtLowerBookend_playsDragThresholdLimitToken() =
+        testScope.runTest {
+            sliderHapticFeedbackProvider.onLowerBookend()
+
+            assertThat(msdlPlayer.latestTokenPlayed)
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() =
         with(kosmos) {
             val vibration =
@@ -94,6 +131,20 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtLowerBookend_twoTimes_playsDragThresholdLimitTokenOnlyOnce() =
+        testScope.runTest {
+            sliderHapticFeedbackProvider.onLowerBookend()
+            sliderHapticFeedbackProvider.onLowerBookend()
+
+            assertThat(msdlPlayer.latestTokenPlayed)
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtUpperBookend_playsClick() =
         with(kosmos) {
             val vibration =
@@ -110,6 +161,18 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtUpperBookend_playsDragThresholdLimitToken() =
+        testScope.runTest {
+            sliderHapticFeedbackProvider.onUpperBookend()
+
+            assertThat(msdlPlayer.latestTokenPlayed)
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() =
         with(kosmos) {
             val vibration =
@@ -127,6 +190,20 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtUpperBookend_twoTimes_playsDragThresholdLimitTokenOnlyOnce() =
+        testScope.runTest {
+            sliderHapticFeedbackProvider.onUpperBookend()
+            sliderHapticFeedbackProvider.onUpperBookend()
+
+            assertThat(msdlPlayer.latestTokenPlayed)
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() =
         with(kosmos) {
             // GIVEN max velocity and slider progress
@@ -150,6 +227,31 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtProgress_onQuickSuccession_playsContinuousDragTokenOnce() =
+        with(kosmos) {
+            // GIVEN max velocity and slider progress
+            val progress = 1f
+            val expectedScale =
+                sliderHapticFeedbackProvider.scaleOnDragTexture(config.maxVelocityToScale, progress)
+            val expectedProperties =
+                InteractionProperties.DynamicVibrationScale(expectedScale, pipeliningAttributes)
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN two calls to play occur immediately
+            sliderHapticFeedbackProvider.onProgress(progress)
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // THEN the correct token plays once
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() =
         with(kosmos) {
             // GIVEN max velocity and a slider progress at half progress
@@ -175,6 +277,41 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtProgress_beforeNextDragThreshold_playsContinousDragTokenOnce() =
+        with(kosmos) {
+            // GIVEN max velocity and a slider progress at half progress
+            val firstProgress = 0.5f
+
+            // Given a second slider progress event smaller than the progress threshold
+            val secondProgress =
+                firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN two calls to play occur with the required threshold separation (time and
+            // progress)
+            sliderHapticFeedbackProvider.onProgress(firstProgress)
+            fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+            sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+            // THEN Only the first event plays the expected token and propertiesv
+            val expectedProperties =
+                InteractionProperties.DynamicVibrationScale(
+                    sliderHapticFeedbackProvider.scaleOnDragTexture(
+                        config.maxVelocityToScale,
+                        firstProgress,
+                    ),
+                    pipeliningAttributes,
+                )
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() =
         with(kosmos) {
             // GIVEN max velocity and a slider progress at half progress
@@ -200,6 +337,51 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtProgress_afterNextDragThreshold_playsContinuousDragTokenTwice() =
+        with(kosmos) {
+            // GIVEN max velocity and a slider progress at half progress
+            val firstProgress = 0.5f
+
+            // Given a second slider progress event beyond progress threshold
+            val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN two calls to play occur with the required threshold separation (time and
+            // progress)
+            sliderHapticFeedbackProvider.onProgress(firstProgress)
+            fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+            sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+            // THEN the correct token plays twice with the correct properties
+            val firstProperties =
+                InteractionProperties.DynamicVibrationScale(
+                    sliderHapticFeedbackProvider.scaleOnDragTexture(
+                        config.maxVelocityToScale,
+                        firstProgress,
+                    ),
+                    pipeliningAttributes,
+                )
+            val secondProperties =
+                InteractionProperties.DynamicVibrationScale(
+                    sliderHapticFeedbackProvider.scaleOnDragTexture(
+                        config.maxVelocityToScale,
+                        secondProgress,
+                    ),
+                    pipeliningAttributes,
+                )
+
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
+            assertThat(msdlPlayer.tokensPlayed[0]).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+            assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(firstProperties)
+            assertThat(msdlPlayer.tokensPlayed[1]).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+            assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(secondProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() =
         with(kosmos) {
             // GIVEN max velocity and slider progress
@@ -233,6 +415,36 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTokensTwice() =
+        with(kosmos) {
+            // GIVEN max velocity and slider progress
+            val progress = 1f
+            val expectedProperties =
+                InteractionProperties.DynamicVibrationScale(
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+                    pipeliningAttributes,
+                )
+
+            // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
+            sliderHapticFeedbackProvider.onLowerBookend()
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // WHEN a vibration is to trigger again at the lower bookend
+            sliderHapticFeedbackProvider.onLowerBookend()
+
+            // THEN there are two bookend token vibrations
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
+            assertThat(msdlPlayer.tokensPlayed[0])
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(expectedProperties)
+            assertThat(msdlPlayer.tokensPlayed[1])
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(expectedProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() =
         with(kosmos) {
             // GIVEN max velocity and slider progress
@@ -265,6 +477,36 @@
             )
         }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTokensTwice() =
+        with(kosmos) {
+            // GIVEN max velocity and slider progress
+            val progress = 1f
+            val expectedProperties =
+                InteractionProperties.DynamicVibrationScale(
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+                    pipeliningAttributes,
+                )
+
+            // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
+            sliderHapticFeedbackProvider.onUpperBookend()
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // WHEN a vibration is to trigger again at the upper bookend
+            sliderHapticFeedbackProvider.onUpperBookend()
+
+            // THEN there are two bookend vibrations
+            assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
+            assertThat(msdlPlayer.tokensPlayed[0])
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(expectedProperties)
+            assertThat(msdlPlayer.tokensPlayed[1])
+                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+            assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(expectedProperties)
+        }
+
+    @Test
     fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() =
         with(kosmos) {
             // GIVEN max velocity and a slider progress at half progress
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 93754fd..77f1979 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -314,16 +314,6 @@
         }
 
     @Test
-    fun isActiveDreamLockscreenHosted() =
-        testScope.runTest {
-            underTest.setIsActiveDreamLockscreenHosted(true)
-            assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(true)
-
-            underTest.setIsActiveDreamLockscreenHosted(false)
-            assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(false)
-        }
-
-    @Test
     fun isUdfpsSupported() =
         testScope.runTest {
             whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
@@ -336,26 +326,14 @@
     @Test
     fun isKeyguardGoingAway() =
         testScope.runTest {
-            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
-            var latest: Boolean? = null
-            val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
-            runCurrent()
-            assertThat(latest).isFalse()
+            val isGoingAway by collectLastValue(underTest.isKeyguardGoingAway)
+            assertThat(isGoingAway).isFalse()
 
-            val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
+            underTest.isKeyguardGoingAway.value = true
+            assertThat(isGoingAway).isTrue()
 
-            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
-            captor.value.onKeyguardGoingAwayChanged()
-            runCurrent()
-            assertThat(latest).isTrue()
-
-            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
-            captor.value.onKeyguardGoingAwayChanged()
-            runCurrent()
-            assertThat(latest).isFalse()
-
-            job.cancel()
+            underTest.isKeyguardGoingAway.value = false
+            assertThat(isGoingAway).isFalse()
         }
 
     @Test
@@ -423,17 +401,17 @@
             runCurrent()
             listener.onDozeTransition(
                 DozeMachine.State.DOZE_REQUEST_PULSE,
-                DozeMachine.State.DOZE_PULSING
+                DozeMachine.State.DOZE_PULSING,
             )
             runCurrent()
             listener.onDozeTransition(
                 DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
-                DozeMachine.State.DOZE_PULSE_DONE
+                DozeMachine.State.DOZE_PULSE_DONE,
             )
             runCurrent()
             listener.onDozeTransition(
                 DozeMachine.State.DOZE_AOD_PAUSING,
-                DozeMachine.State.DOZE_AOD_PAUSED
+                DozeMachine.State.DOZE_AOD_PAUSED,
             )
             runCurrent()
 
@@ -443,22 +421,22 @@
                         // Initial value will be UNINITIALIZED
                         DozeTransitionModel(
                             DozeStateModel.UNINITIALIZED,
-                            DozeStateModel.UNINITIALIZED
+                            DozeStateModel.UNINITIALIZED,
                         ),
                         DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
                         DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
                         DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
                         DozeTransitionModel(
                             DozeStateModel.DOZE_REQUEST_PULSE,
-                            DozeStateModel.DOZE_PULSING
+                            DozeStateModel.DOZE_PULSING,
                         ),
                         DozeTransitionModel(
                             DozeStateModel.DOZE_SUSPEND_TRIGGERS,
-                            DozeStateModel.DOZE_PULSE_DONE
+                            DozeStateModel.DOZE_PULSE_DONE,
                         ),
                         DozeTransitionModel(
                             DozeStateModel.DOZE_AOD_PAUSING,
-                            DozeStateModel.DOZE_AOD_PAUSED
+                            DozeStateModel.DOZE_AOD_PAUSED,
                         ),
                     )
                 )
@@ -510,12 +488,7 @@
             // consuming it should handle that properly.
             assertThat(values).isEqualTo(listOf(null))
 
-            listOf(
-                    Point(500, 500),
-                    Point(0, 0),
-                    null,
-                    Point(250, 250),
-                )
+            listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
                 .onEach {
                     facePropertyRepository.setSensorLocation(it)
                     runCurrent()
@@ -551,7 +524,7 @@
                 .onEach { biometricSourceType ->
                     underTest.setBiometricUnlockState(
                         BiometricUnlockMode.NONE,
-                        BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+                        BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType),
                     )
                     runCurrent()
                 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 3b6e5d0..df44425 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -90,6 +90,7 @@
 
     private lateinit var powerInteractor: PowerInteractor
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
 
     companion object {
         @JvmStatic
@@ -106,6 +107,7 @@
     @Before
     fun setup() {
         powerInteractor = kosmos.powerInteractor
+        keyguardInteractor = kosmos.keyguardInteractor
         transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
         underTest = kosmos.fromDozingTransitionInteractor
 
@@ -137,6 +139,20 @@
         }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToGone_onWakeup_whenGoingAway() =
+        testScope.runTest {
+            keyguardInteractor.setIsKeyguardGoingAway(true)
+            runCurrent()
+
+            powerInteractor.setAwakeForTest()
+            advanceTimeBy(60L)
+
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+        }
+
+    @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index 4862104..e60d971 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -173,17 +173,6 @@
 
     @Test
     @EnableSceneContainer
-    fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() =
-        testScope.runTest {
-            val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeLayoutWide(true)
-            kosmos.activeNotificationListRepository.setActiveNotifs(1)
-            kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            assertThat(value).isTrue()
-        }
-
-    @Test
-    @EnableSceneContainer
     fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index 945e44a..fbdab7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -53,6 +54,7 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardKeyEventInteractorTest : SysuiTestCase() {
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private val kosmos = testKosmos()
 
     private val actionDownVolumeDownKeyEvent =
         KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
@@ -85,6 +87,7 @@
                 mediaSessionLegacyHelperWrapper,
                 backActionInteractor,
                 powerInteractor,
+                kosmos.keyguardMediaKeyInteractor,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorTest.kt
new file mode 100644
index 0000000..b82e154
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
+import android.view.KeyEvent.KEYCODE_MEDIA_PAUSE
+import android.view.KeyEvent.KEYCODE_MEDIA_PLAY
+import android.view.KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.fakeAudioRepository
+import com.google.common.truth.Correspondence.transforming
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMPOSE_BOUNCER)
+class KeyguardMediaKeyInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val underTest = kosmos.keyguardMediaKeyInteractor
+
+    @Before
+    fun setup() {
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun test_onKeyEvent_playPauseKeyEvents_areSkipped_whenACallIsActive() =
+        testScope.runTest {
+            kosmos.fakeTelephonyRepository.setIsInCall(true)
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY))
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PAUSE))
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY_PAUSE))
+
+            assertThat(kosmos.fakeAudioRepository.dispatchedKeyEvents).isEmpty()
+        }
+
+    @Test
+    fun test_onKeyEvent_playPauseKeyEvents_areNotSkipped_whenACallIsNotActive() =
+        testScope.runTest {
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
+
+            assertEventNotConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PAUSE))
+            assertEventConsumed(KeyEvent(ACTION_UP, KEYCODE_MEDIA_PAUSE))
+            assertEventNotConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY))
+            assertEventConsumed(KeyEvent(ACTION_UP, KEYCODE_MEDIA_PLAY))
+            assertEventNotConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY_PAUSE))
+            assertEventConsumed(KeyEvent(ACTION_UP, KEYCODE_MEDIA_PLAY_PAUSE))
+
+            assertThat(kosmos.fakeAudioRepository.dispatchedKeyEvents)
+                .comparingElementsUsing<KeyEvent, Pair<Int, Int>>(
+                    transforming({ Pair(it!!.action, it.keyCode) }, "action and keycode")
+                )
+                .containsExactly(
+                    Pair(ACTION_UP, KEYCODE_MEDIA_PAUSE),
+                    Pair(ACTION_UP, KEYCODE_MEDIA_PLAY),
+                    Pair(ACTION_UP, KEYCODE_MEDIA_PLAY_PAUSE),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun test_onKeyEvent_nonPlayPauseKeyEvents_areNotSkipped_whenACallIsActive() =
+        testScope.runTest {
+            kosmos.fakeTelephonyRepository.setIsInCall(true)
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MUTE))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MUTE))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_REWIND))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_REWIND))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_RECORD))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_RECORD))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
+
+            assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK))
+            assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK))
+
+            assertThat(kosmos.fakeAudioRepository.dispatchedKeyEvents)
+                .comparingElementsUsing<KeyEvent, Pair<Int, Int>>(
+                    transforming({ Pair(it!!.action, it.keyCode) }, "action and keycode")
+                )
+                .containsExactly(
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MUTE),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MUTE),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_REWIND),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_REWIND),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_RECORD),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_RECORD),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD),
+                    Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK),
+                    Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun volumeKeyEvents_keyEvents_areSkipped() =
+        testScope.runTest {
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
+
+            assertEventNotConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP))
+            assertEventNotConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP))
+            assertEventNotConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN))
+            assertEventNotConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_VOLUME_DOWN))
+            assertEventNotConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE))
+            assertEventNotConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_VOLUME_MUTE))
+        }
+
+    private fun assertEventConsumed(keyEvent: KeyEvent) {
+        assertThat(underTest.processMediaKeyEvent(keyEvent)).isTrue()
+    }
+
+    private fun assertEventNotConsumed(keyEvent: KeyEvent) {
+        assertThat(underTest.processMediaKeyEvent(keyEvent)).isFalse()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
deleted file mode 100644
index 28d53c7..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
+++ /dev/null
@@ -1,40 +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.screenshot
-
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-
-internal class FakeScreenshotPolicy : ScreenshotPolicy {
-
-    private val userTypes = mutableMapOf<Int, Boolean>()
-    private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>()
-
-    fun setManagedProfile(userId: Int, managedUser: Boolean) {
-        userTypes[userId] = managedUser
-    }
-    override suspend fun isManagedProfile(userId: Int): Boolean {
-        return userTypes[userId] ?: error("No managedProfile value set for userId $userId")
-    }
-
-    fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) {
-        this.contentInfo[userId] = contentInfo
-    }
-
-    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
-        return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId")
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index fb91c78..080f46f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.android.msdl.domain.MSDLPlayer
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -46,34 +47,26 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.notNull
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class BrightnessSliderControllerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var brightnessSliderView: BrightnessSliderView
-    @Mock
-    private lateinit var enforcedAdmin: RestrictedLockUtils.EnforcedAdmin
-    @Mock
-    private lateinit var mirrorController: BrightnessMirrorController
-    @Mock
-    private lateinit var mirror: ToggleSlider
-    @Mock
-    private lateinit var motionEvent: MotionEvent
-    @Mock
-    private lateinit var listener: ToggleSlider.Listener
-    @Mock
-    private lateinit var vibratorHelper: VibratorHelper
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var brightnessSliderView: BrightnessSliderView
+    @Mock private lateinit var enforcedAdmin: RestrictedLockUtils.EnforcedAdmin
+    @Mock private lateinit var mirrorController: BrightnessMirrorController
+    @Mock private lateinit var mirror: ToggleSlider
+    @Mock private lateinit var motionEvent: MotionEvent
+    @Mock private lateinit var listener: ToggleSlider.Listener
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var msdlPlayer: MSDLPlayer
+    @Mock private lateinit var activityStarter: ActivityStarter
 
     @Captor
     private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
-    @Mock
-    private lateinit var seekBar: SeekBar
+    @Mock private lateinit var seekBar: SeekBar
     private val uiEventLogger = UiEventLoggerFake()
     private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
     private val systemClock = FakeSystemClock()
@@ -93,7 +86,7 @@
                 brightnessSliderView,
                 mFalsingManager,
                 uiEventLogger,
-                SeekbarHapticPlugin(vibratorHelper, systemClock),
+                SeekbarHapticPlugin(vibratorHelper, msdlPlayer, systemClock),
                 activityStarter,
             )
         mController.init()
@@ -241,4 +234,4 @@
         assertThat(uiEventLogger.numLogs()).isEqualTo(1)
         assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
deleted file mode 100644
index 2ac0ed0..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade
-
-import android.os.PowerManager
-import android.view.MotionEvent
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidJUnit4::class)
-class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
-    @Mock private lateinit var falsingManager: FalsingManager
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var statusBarStateController: StatusBarStateController
-    @Mock private lateinit var shadeLogger: ShadeLogger
-    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
-
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-
-    private lateinit var powerRepository: FakePowerRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var underTest: LockscreenHostedDreamGestureListener
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        powerRepository = FakePowerRepository()
-        keyguardRepository = FakeKeyguardRepository()
-
-        underTest =
-            LockscreenHostedDreamGestureListener(
-                falsingManager,
-                PowerInteractorFactory.create(
-                        repository = powerRepository,
-                        statusBarStateController = statusBarStateController,
-                    )
-                    .powerInteractor,
-                statusBarStateController,
-                primaryBouncerInteractor,
-                keyguardRepository,
-                shadeLogger,
-            )
-        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
-    }
-
-    @Test
-    fun testGestureDetector_onSingleTap_whileDreaming() =
-        testScope.runTest {
-            // GIVEN device dreaming and the dream is hosted in lockscreen
-            whenever(statusBarStateController.isDreaming).thenReturn(true)
-            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            testScope.runCurrent()
-
-            // GIVEN the falsing manager does NOT think the tap is a false tap
-            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
-
-            // WHEN there's a tap
-            underTest.onSingleTapUp(upEv)
-
-            // THEN wake up device if dreaming
-            Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
-            Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
-        }
-
-    @Test
-    fun testGestureDetector_onSingleTap_notOnKeyguard() =
-        testScope.runTest {
-            // GIVEN device dreaming and the dream is hosted in lockscreen
-            whenever(statusBarStateController.isDreaming).thenReturn(true)
-            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            testScope.runCurrent()
-
-            // GIVEN shade is open
-            whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
-
-            // GIVEN the falsing manager does NOT think the tap is a false tap
-            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
-
-            // WHEN there's a tap
-            underTest.onSingleTapUp(upEv)
-
-            // THEN the falsing manager never gets a call
-            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
-        }
-
-    @Test
-    fun testGestureDetector_onSingleTap_bouncerShown() =
-        testScope.runTest {
-            // GIVEN device dreaming and the dream is hosted in lockscreen
-            whenever(statusBarStateController.isDreaming).thenReturn(true)
-            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            testScope.runCurrent()
-
-            // GIVEN bouncer is expanded
-            whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
-
-            // GIVEN the falsing manager does NOT think the tap is a false tap
-            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
-
-            // WHEN there's a tap
-            underTest.onSingleTapUp(upEv)
-
-            // THEN the falsing manager never gets a call
-            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
-        }
-
-    @Test
-    fun testGestureDetector_onSingleTap_falsing() =
-        testScope.runTest {
-            // GIVEN device dreaming and the dream is hosted in lockscreen
-            whenever(statusBarStateController.isDreaming).thenReturn(true)
-            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            testScope.runCurrent()
-
-            // GIVEN the falsing manager thinks the tap is a false tap
-            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
-
-            // WHEN there's a tap
-            underTest.onSingleTapUp(upEv)
-
-            // THEN the device doesn't wake up
-            Truth.assertThat(powerRepository.lastWakeWhy).isNull()
-            Truth.assertThat(powerRepository.lastWakeReason).isNull()
-        }
-
-    @Test
-    fun testSingleTap_notDreaming_noFalsingCheck() =
-        testScope.runTest {
-            // GIVEN device not dreaming with lockscreen hosted dream
-            whenever(statusBarStateController.isDreaming).thenReturn(false)
-            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
-            testScope.runCurrent()
-
-            // WHEN there's a tap
-            underTest.onSingleTapUp(upEv)
-
-            // THEN the falsing manager never gets a call
-            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
-        }
-}
-
-private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
index 663c341..16da3d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
@@ -70,14 +70,14 @@
         }
 
     private fun createController() =
-        PrivacyDotViewController(
+        PrivacyDotViewControllerImpl(
                 executor,
                 testScope.backgroundScope,
                 statusBarStateController,
                 configurationController,
                 contentInsetsProvider,
                 animationScheduler = mock<SystemStatusAnimationScheduler>(),
-                shadeInteractor = null
+                shadeInteractor = null,
             )
             .also { it.setUiExecutor(executor) }
 
@@ -307,7 +307,7 @@
             newTopLeftView,
             newTopRightView,
             newBottomLeftView,
-            newBottomRightView
+            newBottomRightView,
         )
 
         assertThat((newBottomRightView.layoutParams as FrameLayout.LayoutParams).gravity)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index dc9c22f..f1edb41 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -36,9 +37,11 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.app.Flags;
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
@@ -61,6 +64,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -69,6 +73,8 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -82,10 +88,12 @@
 import org.mockito.Spy;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -93,6 +101,7 @@
 public class PreparationCoordinatorTest extends SysuiTestCase {
     private NotifCollectionListener mCollectionListener;
     private OnBeforeFinalizeFilterListener mBeforeFilterListener;
+    private OnBeforeTransformGroupsListener mBeforeTransformGroupsListener;
     private NotifFilter mUninflatedFilter;
     private NotifFilter mInflationErrorFilter;
     private NotifInflationErrorManager mErrorManager;
@@ -101,6 +110,8 @@
 
     @Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor;
     @Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor;
+    @Captor private ArgumentCaptor<OnBeforeTransformGroupsListener>
+            mBeforeTransformGroupsListenerCaptor;
     @Captor private ArgumentCaptor<NotifInflater.Params> mParamsCaptor;
 
     @Mock private NotifSectioner mNotifSectioner;
@@ -108,13 +119,14 @@
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private IStatusBarService mService;
     @Mock private BindEventManagerImpl mBindEventManagerImpl;
+    @Mock private AppIconProvider mAppIconProvider;
+    @Mock private NotificationIconStyleProvider mNotificationIconStyleProvider;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private SensitiveNotificationProtectionController mSensitiveNotifProtectionController;
     @Mock private Handler mHandler;
     @Mock private SecureSettings mSecureSettings;
     @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
-    @Mock
-    HighPriorityProvider mHighPriorityProvider;
+    @Mock HighPriorityProvider mHighPriorityProvider;
     private SectionStyleProvider mSectionStyleProvider;
     @Mock private UserTracker mUserTracker;
     @Mock private GroupMembershipManager mGroupMembershipManager;
@@ -126,6 +138,11 @@
         return new NotificationEntryBuilder().setSection(mNotifSection);
     }
 
+    @NonNull
+    private GroupEntryBuilder getGroupEntryBuilder() {
+        return new GroupEntryBuilder().setSection(mNotifSection);
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -138,7 +155,7 @@
                 mSectionStyleProvider,
                 mUserTracker,
                 mGroupMembershipManager
-                );
+        );
         mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
         mInflationError = new Exception(TEST_MESSAGE);
         mErrorManager = new NotifInflationErrorManager();
@@ -153,6 +170,8 @@
                 mAdjustmentProvider,
                 mService,
                 mBindEventManagerImpl,
+                mAppIconProvider,
+                mNotificationIconStyleProvider,
                 TEST_CHILD_BIND_CUTOFF,
                 TEST_MAX_GROUP_DELAY);
 
@@ -163,6 +182,15 @@
         mInflationErrorFilter = filters.get(0);
         mUninflatedFilter = filters.get(1);
 
+        if (android.app.Flags.notificationsRedesignAppIcons()) {
+            verify(mNotifPipeline).addOnBeforeTransformGroupsListener(
+                    mBeforeTransformGroupsListenerCaptor.capture());
+            mBeforeTransformGroupsListener = mBeforeTransformGroupsListenerCaptor.getValue();
+        } else {
+            verify(mNotifPipeline, never()).addOnBeforeTransformGroupsListener(
+                    mBeforeTransformGroupsListenerCaptor.capture());
+        }
+
         verify(mNotifPipeline).addCollectionListener(mCollectionListenerCaptor.capture());
         mCollectionListener = mCollectionListenerCaptor.getValue();
 
@@ -199,6 +227,100 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_APP_ICONS)
+    public void testPurgesAppIconProviderCache() {
+        // GIVEN a notification list
+        NotificationEntry entry1 = getNotificationEntryBuilder().setPkg("1").build();
+        NotificationEntry entry2 = getNotificationEntryBuilder().setPkg("2").build();
+        NotificationEntry entry2bis = getNotificationEntryBuilder().setPkg("2").build();
+        NotificationEntry entry3 = getNotificationEntryBuilder().setPkg("3").build();
+
+        String groupKey1 = "group1";
+        NotificationEntry summary =
+                getNotificationEntryBuilder()
+                        .setPkg(groupKey1)
+                        .setGroup(mContext, groupKey1)
+                        .setGroupSummary(mContext, true)
+                        .build();
+        NotificationEntry child1 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+                .setPkg(groupKey1).build();
+        NotificationEntry child2 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+                .setPkg(groupKey1).build();
+        GroupEntry groupWithSummaryAndChildren = getGroupEntryBuilder().setKey(groupKey1)
+                .setSummary(summary).addChild(child1).addChild(child2).build();
+
+        String groupKey2 = "group2";
+        NotificationEntry summary2 =
+                getNotificationEntryBuilder()
+                        .setPkg(groupKey2)
+                        .setGroup(mContext, groupKey2)
+                        .setGroupSummary(mContext, true)
+                        .build();
+        GroupEntry summaryOnlyGroup = getGroupEntryBuilder().setKey(groupKey2)
+                .setSummary(summary2).build();
+
+        // WHEN onBeforeTransformGroup is called
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(
+                List.of(entry1, entry2, entry2bis, entry3,
+                        groupWithSummaryAndChildren, summaryOnlyGroup));
+
+        // THEN purge should be called
+        ArgumentCaptor<Collection<String>> argumentCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mAppIconProvider).purgeCache(argumentCaptor.capture());
+        List<String> actualList = argumentCaptor.getValue().stream().sorted().toList();
+        List<String> expectedList = Stream.of("1", "2", "3", "group1", "group2")
+                .sorted().toList();
+        assertEquals(expectedList, actualList);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_APP_ICONS)
+    public void testPurgesNotificationIconStyleProviderCache() {
+        // GIVEN a notification list
+        NotificationEntry entry1 = getNotificationEntryBuilder().setPkg("1").build();
+        NotificationEntry entry2 = getNotificationEntryBuilder().setPkg("2").build();
+        NotificationEntry entry2bis = getNotificationEntryBuilder().setPkg("2").build();
+        NotificationEntry entry3 = getNotificationEntryBuilder().setPkg("3").build();
+
+        String groupKey1 = "group1";
+        NotificationEntry summary =
+                getNotificationEntryBuilder()
+                        .setPkg(groupKey1)
+                        .setGroup(mContext, groupKey1)
+                        .setGroupSummary(mContext, true)
+                        .build();
+        NotificationEntry child1 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+                .setPkg(groupKey1).build();
+        NotificationEntry child2 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+                .setPkg(groupKey1).build();
+        GroupEntry groupWithSummaryAndChildren = getGroupEntryBuilder().setKey(groupKey1)
+                .setSummary(summary).addChild(child1).addChild(child2).build();
+
+        String groupKey2 = "group2";
+        NotificationEntry summary2 =
+                getNotificationEntryBuilder()
+                        .setPkg(groupKey2)
+                        .setGroup(mContext, groupKey2)
+                        .setGroupSummary(mContext, true)
+                        .build();
+        GroupEntry summaryOnlyGroup = getGroupEntryBuilder().setKey(groupKey2)
+                .setSummary(summary2).build();
+
+        // WHEN onBeforeTransformGroup is called
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(
+                List.of(entry1, entry2, entry2bis, entry3,
+                        groupWithSummaryAndChildren, summaryOnlyGroup));
+
+        // THEN purge should be called
+        ArgumentCaptor<Collection<String>> argumentCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mNotificationIconStyleProvider).purgeCache(argumentCaptor.capture());
+        List<String> actualList = argumentCaptor.getValue().stream().sorted().toList();
+        List<String> expectedList = Stream.of("1", "2", "3", "group1", "group2")
+                .sorted().toList();
+        assertEquals(expectedList, actualList);
+    }
+
+    @Test
     public void testInflatesNewNotification() {
         // WHEN there is a new notification
         mCollectionListener.onEntryInit(mEntry);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index cfac486..ea5c29e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -66,7 +65,6 @@
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -100,7 +98,6 @@
     @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
     @Mock private VisualStabilityCoordinatorLogger mLogger;
-    @Mock private KeyguardStateController mKeyguardStateController;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
@@ -141,7 +138,6 @@
                 mKosmos.getCommunalSceneInteractor(),
                 mKosmos.getShadeInteractor(),
                 mKosmos.getKeyguardTransitionInteractor(),
-                mKeyguardStateController,
                 mLogger);
         mCoordinator.attach(mNotifPipeline);
         mTestScope.getTestScheduler().runCurrent();
@@ -526,13 +522,23 @@
     @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
     public void testNotLockscreenInGoneTransition_invalidationCalled() {
         // GIVEN visual stability is being maintained b/c animation is playing
-        doReturn(true).when(mKeyguardStateController).isKeyguardFadingAway();
-        mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+        mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+                mTestScope, new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GONE,
+                        1f,
+                        TransitionState.RUNNING),  /* validateStep = */ false);
+        mTestScope.getTestScheduler().runCurrent();
         assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
 
         // WHEN the animation has stopped playing
-        doReturn(false).when(mKeyguardStateController).isKeyguardFadingAway();
-        mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+        mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+                mTestScope, new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GONE,
+                        1f,
+                        TransitionState.FINISHED),  /* validateStep = */ false);
+        mTestScope.getTestScheduler().runCurrent();
 
         // invalidate is called, b/c we were previously suppressing the pipeline from running
         verifyStabilityManagerWasInvalidated(times(1));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index aed9af6..406ca05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -33,6 +33,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
@@ -67,6 +68,8 @@
     @Mock
     private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     @Mock
+    private Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
+    @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private KeyguardUpdateMonitorLogger mLogger;
@@ -86,6 +89,7 @@
                 mKeyguardUnlockAnimationControllerLazy,
                 mLogger,
                 mDumpManager,
+                mKeyguardInteractorLazy,
                 mFeatureFlags,
                 mSelectedUserInteractor);
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
new file mode 100644
index 0000000..7d55599
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.volume.dialog.ringer.domain
+
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.media.AudioManager.STREAM_RING
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogRingerInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val controller = kosmos.fakeVolumeDialogController
+
+    private lateinit var underTest: VolumeDialogRingerInteractor
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.volumeDialogRingerInteractor
+        controller.setStreamVolume(STREAM_RING, 50)
+    }
+
+    @Test
+    fun setRingerMode_normal() =
+        testScope.runTest {
+            runCurrent()
+            val ringerModel by collectLastValue(underTest.ringerModel)
+
+            underTest.setRingerMode(RingerMode(RINGER_MODE_NORMAL))
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerModel).isNotNull()
+            assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_NORMAL))
+        }
+
+    @Test
+    fun setRingerMode_silent() =
+        testScope.runTest {
+            runCurrent()
+            val ringerModel by collectLastValue(underTest.ringerModel)
+
+            underTest.setRingerMode(RingerMode(RINGER_MODE_SILENT))
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerModel).isNotNull()
+            assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_SILENT))
+        }
+
+    @Test
+    fun setRingerMode_vibrate() =
+        testScope.runTest {
+            runCurrent()
+            val ringerModel by collectLastValue(underTest.ringerModel)
+
+            underTest.setRingerMode(RingerMode(RINGER_MODE_VIBRATE))
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerModel).isNotNull()
+            assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_VIBRATE))
+        }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cdf15ca..c494e85 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3745,6 +3745,10 @@
          use. The helper shows shortcuts in categories, which can be collapsed or expanded.
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_content_description_drag_handle">Drag handle</string>
+    <!-- Label on the keyboard settings button in keyboard shortcut helper, that allows user to
+         open keyboard settings while in shortcut helper. The helper is a  component that shows the
+         user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_keyboard_settings_buttons_label">Keyboard Settings</string>
 
 
     <!-- Keyboard touchpad tutorial scheduler-->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 83ab524..b3ea75d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2368,7 +2368,7 @@
         }
 
         // Take a guess at initial SIM state, battery status and PLMN until we get an update
-        mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ 100, /* plugged= */
+        mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ -1, /* plugged= */
                 0, CHARGING_POLICY_DEFAULT, /* maxChargingWattage= */0, /* present= */true);
 
         // Watch for interesting updates
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8ae11ab..811b47d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -43,7 +43,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 import com.android.systemui.util.NotificationChannels;
 
 import java.lang.reflect.InvocationTargetException;
@@ -454,13 +454,13 @@
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         if (mServicesStarted) {
-            ConfigurationController configController = mSysUIComponent.getConfigurationController();
+            ConfigurationForwarder configForwarder = mSysUIComponent.getConfigurationForwarder();
             if (Trace.isEnabled()) {
                 Trace.traceBegin(
                         Trace.TRACE_TAG_APP,
-                        configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+                        configForwarder.getClass().getSimpleName() + ".onConfigurationChanged()");
             }
-            configController.onConfigurationChanged(newConfig);
+            configForwarder.onConfigurationChanged(newConfig);
             Trace.endSection();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/startable/BouncerStartable.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/startable/BouncerStartable.kt
new file mode 100644
index 0000000..e3cf88c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/startable/BouncerStartable.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bouncer.domain.startable
+
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardMediaKeyInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Starts interactors needed for the compose bouncer to work as expected. */
+@SysUISingleton
+class BouncerStartable
+@Inject
+constructor(
+    private val keyguardMediaKeyInteractor: dagger.Lazy<KeyguardMediaKeyInteractor>,
+    @Application private val scope: CoroutineScope,
+) : CoreStartable {
+    override fun start() {
+        if (!ComposeBouncerFlags.isEnabled) return
+
+        scope.launchTraced("KeyguardMediaKeyInteractor#start") {
+            keyguardMediaKeyInteractor.get().activate()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index 7647cf6..47570a5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -43,17 +43,25 @@
     fun isUnexpectedlyInLegacyMode() =
         RefactorFlagUtils.isUnexpectedlyInLegacyMode(
             isEnabled,
-            "SceneContainerFlag || ComposeBouncerFlag"
+            "SceneContainerFlag || ComposeBouncerFlag",
         )
 
     /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    fun assertInLegacyMode() =
+        RefactorFlagUtils.assertInLegacyMode(isEnabled, "SceneContainerFlag || ComposeBouncerFlag")
+
+    /**
      * Returns `true` if only compose bouncer is enabled and scene container framework is not
      * enabled.
      */
     @Deprecated(
         "Avoid using this, this is meant to be used only by the glue code " +
             "that includes compose bouncer in legacy keyguard.",
-        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
+        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()"),
     )
     fun isOnlyComposeBouncerEnabled(): Boolean {
         return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 148b9ea..1bcc1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.annotation.StringRes
+import androidx.compose.ui.input.key.KeyEventType
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -122,6 +123,13 @@
     /** Invoked after a successful authentication. */
     protected open fun onSuccessfulAuthentication() = Unit
 
+    /**
+     * Invoked for any key events on the bouncer.
+     *
+     * @return whether the event was consumed by this method and should not be propagated further.
+     */
+    open fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean = false
+
     /** Perform authentication result haptics */
     private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
         if (result == AuthenticationResult.SKIPPED) return
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 5f97391..67d312e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardMediaKeyInteractor
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import dagger.assisted.AssistedFactory
@@ -63,6 +64,7 @@
     private val patternViewModelFactory: PatternBouncerViewModel.Factory,
     private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
     private val bouncerHapticPlayer: BouncerHapticPlayer,
+    private val keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor,
 ) : ExclusiveActivatable() {
     private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
     val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -337,10 +339,9 @@
      * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
      */
     fun onKeyEvent(keyEvent: KeyEvent): Boolean {
-        return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
-            keyEvent.type,
-            keyEvent.nativeKeyEvent.keyCode,
-        ) ?: false
+        if (keyguardMediaKeyInteractor.processMediaKeyEvent(keyEvent.nativeKeyEvent)) return true
+        return authMethodViewModel.value?.onKeyEvent(keyEvent.type, keyEvent.nativeKeyEvent.keyCode)
+            ?: false
     }
 
     data class DialogViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 1427d78..b8c6ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import android.view.KeyEvent
 import androidx.annotation.VisibleForTesting
+import androidx.compose.ui.input.key.KeyEventType
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -150,6 +153,17 @@
         return _password.value.toCharArray().toList()
     }
 
+    override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
+        // Ignore SPACE as a confirm key to allow the space character within passwords.
+        val isKeyboardEnterKey =
+            KeyEvent.isConfirmKey(keyCode) &&
+                keyCode != KeyEvent.KEYCODE_SPACE &&
+                type == KeyEventType.KeyUp
+        // consume confirm key events while on the bouncer. This prevents it from propagating
+        // and avoids other parent elements from receiving it.
+        return isKeyboardEnterKey && ComposeBouncerFlags.isOnlyComposeBouncerEnabled()
+    }
+
     override fun onSuccessfulAuthentication() {
         wasSuccessfullyAuthenticated = true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 0cb4260..5c8a9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -251,7 +251,7 @@
      *
      * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
      */
-    fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
+    override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
         return when (type) {
             KeyEventType.KeyUp -> {
                 if (isConfirmKey(keyCode)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 3fe6669..17f1961 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -29,6 +29,7 @@
 import com.android.systemui.startable.Dependencies;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -125,13 +126,20 @@
     BootCompleteCacheImpl provideBootCacheImpl();
 
     /**
-     * Creates a ContextComponentHelper.
+     * Creates a ConfigurationController.
      */
     @SysUISingleton
     @GlobalConfig
     ConfigurationController getConfigurationController();
 
     /**
+     * Creates a ConfigurationForwarder.
+     */
+    @SysUISingleton
+    @GlobalConfig
+    ConfigurationForwarder getConfigurationForwarder();
+
+    /**
      * Creates a ContextComponentHelper.
      */
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 0de919d..6fb6236 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.accessibility.Magnification
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.BiometricNotificationService
+import com.android.systemui.bouncer.domain.startable.BouncerStartable
 import com.android.systemui.clipboardoverlay.ClipboardListener
 import com.android.systemui.controls.dagger.StartControlsStartableModule
 import com.android.systemui.dagger.qualifiers.PerUser
@@ -304,6 +305,11 @@
 
     @Binds
     @IntoMap
+    @ClassKey(BouncerStartable::class)
+    abstract fun bindBouncerStartable(impl: BouncerStartable): CoreStartable
+
+    @Binds
+    @IntoMap
     @ClassKey(KeyguardDismissBinder::class)
     abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 21922ff..12718e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -17,6 +17,7 @@
 package com.android.systemui.doze;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.Flags.screenOffUnlockUdfps;
 
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
@@ -248,8 +249,8 @@
                         true /* touchscreen */,
                         false /* ignoresSetting */,
                         dozeParameters.longPressUsesProx(),
-                        false /* immediatelyReRegister */,
-                        true /* requiresAod */
+                        screenOffUnlockUdfps() /* immediatelyReRegister */,
+                        !screenOffUnlockUdfps() /* requiresAod */
                 ),
                 new PluginSensor(
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 95cd9eb..61832875 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -165,7 +165,7 @@
     val QS_USER_DETAIL_SHORTCUT =
         resourceBooleanFlag(
             R.bool.flag_lockscreen_qs_user_detail_shortcut,
-            "qs_user_detail_shortcut"
+            "qs_user_detail_shortcut",
         )
 
     // TODO(b/254512383): Tracking Bug
@@ -365,11 +365,6 @@
     val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
         releasedFlag("zj_285570694_lockscreen_transition_from_aod")
 
-    // 3000 - dream
-    // TODO(b/285059790) : Tracking Bug
-    @JvmField
-    val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream")
-
     // TODO(b/283447257): Tracking bug
     @JvmField
     val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt
index 932e5af..cc77f68a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.time.SystemClock
+import com.google.android.msdl.domain.MSDLPlayer
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
@@ -39,6 +40,7 @@
 @JvmOverloads
 constructor(
     vibratorHelper: VibratorHelper,
+    msdlPlayer: MSDLPlayer,
     systemClock: SystemClock,
     sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
     private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
@@ -63,6 +65,7 @@
     private val sliderHapticFeedbackProvider =
         SliderHapticFeedbackProvider(
             vibratorHelper,
+            msdlPlayer,
             dragVelocityProvider,
             sliderHapticFeedbackConfig,
             systemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index 06428b7..bc4f531 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -22,7 +22,11 @@
 import android.view.animation.AccelerateInterpolator
 import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
 import com.android.systemui.statusbar.VibratorHelper
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
 import kotlin.math.abs
 import kotlin.math.min
 import kotlin.math.pow
@@ -38,6 +42,7 @@
  */
 class SliderHapticFeedbackProvider(
     private val vibratorHelper: VibratorHelper,
+    private val msdlPlayer: MSDLPlayer,
     private val velocityProvider: SliderDragVelocityProvider,
     private val config: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
     private val clock: com.android.systemui.util.time.SystemClock,
@@ -67,11 +72,20 @@
      */
     private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
         val powerScale = scaleOnEdgeCollision(absoluteVelocity)
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
-                .compose()
-        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+        if (Flags.msdlFeedback()) {
+            val properties =
+                InteractionProperties.DynamicVibrationScale(
+                    powerScale,
+                    VIBRATION_ATTRIBUTES_PIPELINING,
+                )
+            msdlPlayer.playToken(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT, properties)
+        } else {
+            val vibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
+                    .compose()
+            vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+        }
     }
 
     /**
@@ -112,16 +126,26 @@
 
         val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)
 
-        // Trigger the vibration composition
-        val composition = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
-        }
-        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+        // Deliver haptic feedback
+        performContinuousSliderDragVibration(powerScale)
         dragTextureLastTime = currentTime
         dragTextureLastProgress = normalizedSliderProgress
     }
 
+    private fun performContinuousSliderDragVibration(scale: Float) {
+        if (Flags.msdlFeedback()) {
+            val properties =
+                InteractionProperties.DynamicVibrationScale(scale, VIBRATION_ATTRIBUTES_PIPELINING)
+            msdlPlayer.playToken(MSDLToken.DRAG_INDICATOR_CONTINUOUS, properties)
+        } else {
+            val composition = VibrationEffect.startComposition()
+            repeat(config.numberOfLowTicks) {
+                composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
+            }
+            vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+        }
+    }
+
     /**
      * Get the scale of the drag texture vibration.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt
index 1dbcb3df..de24259 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.time.SystemClock
+import com.google.android.msdl.domain.MSDLPlayer
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -50,6 +51,7 @@
     @Assisted private val sliderHapticFeedbackConfig: SliderHapticFeedbackConfig,
     @Assisted private val sliderTrackerConfig: SeekableSliderTrackerConfig,
     vibratorHelper: VibratorHelper,
+    msdlPlayer: MSDLPlayer,
     systemClock: SystemClock,
 ) : ExclusiveActivatable() {
 
@@ -78,6 +80,7 @@
     private val sliderHapticFeedbackProvider =
         SliderHapticFeedbackProvider(
             vibratorHelper,
+            msdlPlayer,
             dragVelocityProvider,
             sliderHapticFeedbackConfig,
             systemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 3c8bb09..5cade68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -16,10 +16,7 @@
 
 package com.android.systemui.keyboard.shortcut.ui.composable
 
-import android.content.Context
-import android.content.pm.PackageManager.NameNotFoundException
 import android.graphics.drawable.Icon
-import android.util.Log
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Image
@@ -55,12 +52,8 @@
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.OpenInNew
-import androidx.compose.material.icons.filled.Apps
 import androidx.compose.material.icons.filled.ExpandMore
-import androidx.compose.material.icons.filled.Keyboard
 import androidx.compose.material.icons.filled.Search
-import androidx.compose.material.icons.filled.Tv
-import androidx.compose.material.icons.filled.VerticalSplit
 import androidx.compose.material3.CenterAlignedTopAppBar
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.HorizontalDivider
@@ -111,16 +104,15 @@
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.CentralSurfaces
 
 @Composable
 fun ShortcutHelper(
@@ -187,7 +179,7 @@
 private fun ShortcutHelperSinglePane(
     searchQuery: String,
     onSearchQueryChanged: (String) -> Unit,
-    categories: List<ShortcutCategory>,
+    categories: List<ShortcutCategoryUi>,
     selectedCategoryType: ShortcutCategoryType?,
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
@@ -228,7 +220,7 @@
 @Composable
 private fun CategoriesPanelSinglePane(
     searchQuery: String,
-    categories: List<ShortcutCategory>,
+    categories: List<ShortcutCategoryUi>,
     selectedCategoryType: ShortcutCategoryType?,
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
 ) {
@@ -267,7 +259,7 @@
 @Composable
 private fun CategoryItemSinglePane(
     searchQuery: String,
-    category: ShortcutCategory,
+    category: ShortcutCategoryUi,
     isExpanded: Boolean,
     onClick: () -> Unit,
     shape: Shape,
@@ -278,9 +270,9 @@
                 verticalAlignment = Alignment.CenterVertically,
                 modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp),
             ) {
-                ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon)
+                ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource)
                 Spacer(modifier = Modifier.width(16.dp))
-                Text(category.label(LocalContext.current))
+                Text(category.label)
                 Spacer(modifier = Modifier.weight(1f))
                 RotatingExpandCollapseIcon(isExpanded)
             }
@@ -291,23 +283,6 @@
     }
 }
 
-private val ShortcutCategory.icon: IconSource
-    @Composable
-    get() =
-        when (type) {
-            ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
-            ShortcutCategoryType.MultiTasking ->
-                IconSource(imageVector = Icons.Default.VerticalSplit)
-            ShortcutCategoryType.InputMethodEditor ->
-                IconSource(imageVector = Icons.Default.Keyboard)
-            ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
-            is ShortcutCategoryType.CurrentApp -> {
-                val context = LocalContext.current
-                val iconDrawable = context.packageManager.getApplicationIcon(type.packageName)
-                IconSource(painter = rememberDrawablePainter(drawable = iconDrawable))
-            }
-        }
-
 @Composable
 fun ShortcutCategoryIcon(
     source: IconSource,
@@ -322,37 +297,6 @@
     }
 }
 
-private fun ShortcutCategory.label(context: Context): String =
-    when (type) {
-        ShortcutCategoryType.System -> context.getString(R.string.shortcut_helper_category_system)
-        ShortcutCategoryType.MultiTasking ->
-            context.getString(R.string.shortcut_helper_category_multitasking)
-        ShortcutCategoryType.InputMethodEditor ->
-            context.getString(R.string.shortcut_helper_category_input)
-        ShortcutCategoryType.AppCategories ->
-            context.getString(R.string.shortcut_helper_category_app_shortcuts)
-        is ShortcutCategoryType.CurrentApp -> getApplicationLabelForCurrentApp(type, context)
-    }
-
-private fun getApplicationLabelForCurrentApp(
-    type: ShortcutCategoryType.CurrentApp,
-    context: Context,
-): String {
-    val packageManagerForUser = CentralSurfaces.getPackageManagerForUser(context, context.userId)
-    return try {
-        val currentAppInfo =
-            packageManagerForUser.getApplicationInfoAsUser(
-                type.packageName,
-                /* flags = */ 0,
-                context.userId,
-            )
-        packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
-    } catch (e: NameNotFoundException) {
-        Log.wtf(ShortcutHelper.TAG, "Couldn't find app info by package name ${type.packageName}")
-        context.getString(R.string.shortcut_helper_category_current_app_shortcuts)
-    }
-}
-
 @Composable
 private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
     val expandIconRotationDegrees by
@@ -384,7 +328,7 @@
 }
 
 @Composable
-private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategory) {
+private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) {
     Column(Modifier.padding(horizontal = 16.dp)) {
         category.subCategories.fastForEach { subCategory ->
             ShortcutSubCategorySinglePane(searchQuery, subCategory)
@@ -409,7 +353,7 @@
     searchQuery: String,
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier = Modifier,
-    categories: List<ShortcutCategory>,
+    categories: List<ShortcutCategoryUi>,
     selectedCategoryType: ShortcutCategoryType?,
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
@@ -434,7 +378,7 @@
 }
 
 @Composable
-private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
+private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?) {
     val listState = rememberLazyListState()
     LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
     if (category == null) {
@@ -670,10 +614,10 @@
 private fun StartSidePanel(
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
-    categories: List<ShortcutCategory>,
+    categories: List<ShortcutCategoryUi>,
     onKeyboardSettingsClicked: () -> Unit,
     selectedCategory: ShortcutCategoryType?,
-    onCategoryClicked: (ShortcutCategory) -> Unit,
+    onCategoryClicked: (ShortcutCategoryUi) -> Unit,
 ) {
     Column(modifier) {
         ShortcutsSearchBar(onSearchQueryChanged)
@@ -690,15 +634,15 @@
 
 @Composable
 private fun CategoriesPanelTwoPane(
-    categories: List<ShortcutCategory>,
+    categories: List<ShortcutCategoryUi>,
     selectedCategory: ShortcutCategoryType?,
-    onCategoryClicked: (ShortcutCategory) -> Unit,
+    onCategoryClicked: (ShortcutCategoryUi) -> Unit,
 ) {
     Column {
         categories.fastForEach {
             CategoryItemTwoPane(
-                label = it.label(LocalContext.current),
-                iconSource = it.icon,
+                label = it.label,
+                iconSource = it.iconSource,
                 selected = selectedCategory == it.type,
                 onClick = { onCategoryClicked(it) },
             )
@@ -833,7 +777,7 @@
     ) {
         Row(verticalAlignment = Alignment.CenterVertically) {
             Text(
-                "Keyboard Settings",
+                stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label),
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
                 fontSize = 16.sp,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
new file mode 100644
index 0000000..f5d478b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyboard.shortcut.ui.model
+
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+
+data class ShortcutCategoryUi(
+    val label: String,
+    val iconSource: IconSource,
+    val type: ShortcutCategoryType,
+    val subCategories: List<ShortcutSubCategory>,
+) {
+    constructor(
+        label: String,
+        iconSource: IconSource,
+        shortcutCategory: ShortcutCategory,
+    ) : this(label, iconSource, shortcutCategory.type, shortcutCategory.subCategories)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index d2122b3..8f23261 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -16,14 +16,13 @@
 
 package com.android.systemui.keyboard.shortcut.ui.model
 
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 
 sealed interface ShortcutsUiState {
 
     data class Active(
         val searchQuery: String,
-        val shortcutCategories: List<ShortcutCategory>,
+        val shortcutCategories: List<ShortcutCategoryUi>,
         val defaultSelectedCategory: ShortcutCategoryType?,
     ) : ShortcutsUiState
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 04aa04d..20d09ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -17,6 +17,15 @@
 package com.android.systemui.keyboard.shortcut.ui.viewmodel
 
 import android.app.role.RoleManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.util.Log
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Android
+import androidx.compose.material.icons.filled.Apps
+import androidx.compose.material.icons.filled.Keyboard
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
+import com.android.compose.ui.graphics.painter.DrawablePainter
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -25,7 +34,10 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -51,6 +63,7 @@
 ) {
 
     private val searchQuery = MutableStateFlow("")
+    private val userContext = userTracker.createCurrentUserContext(userTracker.userContext)
 
     val shouldShow =
         categoriesInteractor.shortcutCategories
@@ -68,9 +81,10 @@
                     val categoriesWithLauncherExcluded = excludeLauncherApp(categories)
                     val filteredCategories =
                         filterCategoriesBySearchQuery(query, categoriesWithLauncherExcluded)
+                    val shortcutCategoriesUi = convertCategoriesModelToUiModel(filteredCategories)
                     ShortcutsUiState.Active(
                         searchQuery = query,
-                        shortcutCategories = filteredCategories,
+                        shortcutCategories = shortcutCategoriesUi,
                         defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
                     )
                 }
@@ -78,9 +92,73 @@
             .stateIn(
                 scope = backgroundScope,
                 started = SharingStarted.Lazily,
-                initialValue = ShortcutsUiState.Inactive
+                initialValue = ShortcutsUiState.Inactive,
             )
 
+    private fun convertCategoriesModelToUiModel(
+        categories: List<ShortcutCategory>
+    ): List<ShortcutCategoryUi> {
+        return categories.map { category ->
+            ShortcutCategoryUi(
+                label = getShortcutCategoryLabel(category.type),
+                iconSource = getShortcutCategoryIcon(category.type),
+                shortcutCategory = category,
+            )
+        }
+    }
+
+    private fun getShortcutCategoryIcon(type: ShortcutCategoryType): IconSource {
+        return when (type) {
+            ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
+            ShortcutCategoryType.MultiTasking ->
+                IconSource(imageVector = Icons.Default.VerticalSplit)
+            ShortcutCategoryType.InputMethodEditor ->
+                IconSource(imageVector = Icons.Default.Keyboard)
+            ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
+            is CurrentApp -> {
+                try {
+                    val iconDrawable =
+                        userContext.packageManager.getApplicationIcon(type.packageName)
+                    IconSource(painter = DrawablePainter(drawable = iconDrawable))
+                } catch (e: NameNotFoundException) {
+                    Log.wtf(
+                        "ShortcutHelperViewModel",
+                        "Package not found when retrieving icon for ${type.packageName}",
+                    )
+                    IconSource(imageVector = Icons.Default.Android)
+                }
+            }
+        }
+    }
+
+    private fun getShortcutCategoryLabel(type: ShortcutCategoryType): String =
+        when (type) {
+            ShortcutCategoryType.System ->
+                userContext.getString(R.string.shortcut_helper_category_system)
+            ShortcutCategoryType.MultiTasking ->
+                userContext.getString(R.string.shortcut_helper_category_multitasking)
+            ShortcutCategoryType.InputMethodEditor ->
+                userContext.getString(R.string.shortcut_helper_category_input)
+            ShortcutCategoryType.AppCategories ->
+                userContext.getString(R.string.shortcut_helper_category_app_shortcuts)
+            is CurrentApp -> getApplicationLabelForCurrentApp(type)
+        }
+
+    private fun getApplicationLabelForCurrentApp(type: CurrentApp): String {
+        try {
+            val packageManagerForUser = userContext.packageManager
+            val currentAppInfo =
+                packageManagerForUser.getApplicationInfo(type.packageName, /* flags= */ 0)
+            return packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
+        } catch (e: NameNotFoundException) {
+            Log.wtf(
+                "ShortcutHelperViewModel",
+                "Package Not found when retrieving Label for ${type.packageName}",
+            )
+            return "Current App"
+        }
+    }
+
     private suspend fun excludeLauncherApp(
         categories: List<ShortcutCategory>
     ): List<ShortcutCategory> {
@@ -111,7 +189,7 @@
 
     private fun filterCategoriesBySearchQuery(
         query: String,
-        categories: List<ShortcutCategory>
+        categories: List<ShortcutCategory>,
     ): List<ShortcutCategory> {
         val lowerCaseTrimmedQuery = query.trim().lowercase()
         if (lowerCaseTrimmedQuery.isEmpty()) {
@@ -132,7 +210,7 @@
 
     private fun filterSubCategoriesBySearchQuery(
         subCategories: List<ShortcutSubCategory>,
-        query: String
+        query: String,
     ) =
         subCategories
             .map { subCategory ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fbc76c5..60a306b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2975,7 +2975,7 @@
         @Override
         public void run() {
             Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
-            if (DEBUG) Log.d(TAG, "keyguardGoingAway");
+            Log.d(TAG, "keyguardGoingAwayRunnable");
             mKeyguardViewControllerLazy.get().keyguardGoingAway();
 
             int flags = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 8210174..9e99a87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -114,7 +114,7 @@
             "away' is isInTransitionToState(GONE), but consider using more specific flows " +
             "whenever possible."
     )
-    val isKeyguardGoingAway: Flow<Boolean>
+    val isKeyguardGoingAway: MutableStateFlow<Boolean>
 
     /**
      * Whether the keyguard is enabled, per [KeyguardService]. If the keyguard is not enabled, the
@@ -184,9 +184,6 @@
     /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
     val isDreamingWithOverlay: Flow<Boolean>
 
-    /** Observable for device dreaming state and the active dream is hosted in lockscreen */
-    val isActiveDreamLockscreenHosted: StateFlow<Boolean>
-
     /**
      * Observable for the amount of doze we are currently in.
      *
@@ -308,8 +305,6 @@
 
     fun setIsDozing(isDozing: Boolean)
 
-    fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
-
     fun dozeTimeTick()
 
     fun showDismissibleKeyguard()
@@ -637,9 +632,6 @@
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
-    private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
-    override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
-
     private val _shortcutAbsoluteTop = MutableStateFlow(0F)
     override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
 
@@ -655,10 +647,6 @@
                 override fun onUnlockedChanged() {
                     isKeyguardDismissible.value = keyguardStateController.isUnlocked
                 }
-
-                override fun onKeyguardGoingAwayChanged() {
-                    isKeyguardGoingAway.value = keyguardStateController.isKeyguardGoingAway
-                }
             }
 
         keyguardStateController.addCallback(callback)
@@ -698,10 +686,6 @@
         _isQuickSettingsVisible.value = isVisible
     }
 
-    override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
-        _isActiveDreamLockscreenHosted.value = isLockscreenHosted
-    }
-
     override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
         _clockShouldBeCentered.value = shouldBeCentered
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 8c7fe5f..0c2d577 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -131,12 +131,13 @@
                 .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
                     val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
                     val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+                    val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
 
                     if (!deviceEntryInteractor.isLockscreenEnabled()) {
                         if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GONE)
                         }
-                    } else if (canDismissLockscreen()) {
+                    } else if (canDismissLockscreen() || isKeyguardGoingAway) {
                         if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GONE)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 5b7eedd..d18d6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -105,21 +105,13 @@
             combine(
                 shadeInteractor.isShadeLayoutWide,
                 activeNotificationsInteractor.areAnyNotificationsPresent,
-                keyguardInteractor.isActiveDreamLockscreenHosted,
                 isOnAod,
                 headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
                 keyguardInteractor.isDozing,
-            ) {
-                isShadeLayoutWide,
-                areAnyNotificationsPresent,
-                isActiveDreamLockscreenHosted,
-                isOnAod,
-                isHeadsUp,
-                isDozing ->
+            ) { isShadeLayoutWide, areAnyNotificationsPresent, isOnAod, isHeadsUp, isDozing ->
                 when {
                     !isShadeLayoutWide -> true
                     !areAnyNotificationsPresent -> true
-                    isActiveDreamLockscreenHosted -> true
                     // Pulsing notification appears on the right. Move clock left to avoid overlap.
                     isHeadsUp && isDozing -> false
                     else -> isOnAod
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6ecbc61..0d5ad54 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -189,9 +189,6 @@
     /** Whether any dreaming is running, including the doze dream. */
     val isDreamingAny: Flow<Boolean> = repository.isDreaming
 
-    /** Whether the system is dreaming and the active dream is hosted in lockscreen */
-    val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
-
     /** Event for when the camera gesture is detected */
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
         repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
@@ -244,7 +241,7 @@
 
     /** Whether the keyguard is going away. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
-    val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+    val isKeyguardGoingAway: StateFlow<Boolean> = repository.isKeyguardGoingAway.asStateFlow()
 
     /** Keyguard can be clipped at the top as the shade is dragged */
     val topClippingBounds: Flow<Int?> by lazy {
@@ -477,10 +474,6 @@
         }
     }
 
-    fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
-        repository.setIsActiveDreamLockscreenHosted(isLockscreenHosted)
-    }
-
     /** Sets whether quick settings or quick-quick settings is visible. */
     fun setQuickSettingsVisible(isVisible: Boolean) {
         repository.setQuickSettingsVisible(isVisible)
@@ -549,6 +542,10 @@
         repository.setShortcutAbsoluteTop(top)
     }
 
+    fun setIsKeyguardGoingAway(isGoingAway: Boolean) {
+        repository.isKeyguardGoingAway.value = isGoingAway
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index fcf486b..d4d7e75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -20,6 +20,7 @@
 import android.media.AudioManager
 import android.view.KeyEvent
 import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler.Companion.handleAction
 import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
@@ -45,6 +46,7 @@
     private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
     private val backActionInteractor: BackActionInteractor,
     private val powerInteractor: PowerInteractor,
+    private val keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor,
 ) {
 
     fun dispatchKeyEvent(event: KeyEvent): Boolean {
@@ -96,8 +98,15 @@
     }
 
     fun interceptMediaKey(event: KeyEvent): Boolean {
-        return statusBarStateController.state == StatusBarState.KEYGUARD &&
-            statusBarKeyguardViewManager.interceptMediaKey(event)
+        return when (statusBarStateController.state) {
+            StatusBarState.KEYGUARD ->
+                if (ComposeBouncerFlags.isEnabled) {
+                    keyguardMediaKeyInteractor.processMediaKeyEvent(event)
+                } else {
+                    statusBarKeyguardViewManager.interceptMediaKey(event)
+                }
+            else -> false
+        }
     }
 
     private fun dispatchMenuKeyEvent(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractor.kt
new file mode 100644
index 0000000..1404ef6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractor.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.media.AudioManager
+import android.view.KeyEvent
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import javax.inject.Inject
+
+/** Handle media key events while on keyguard or bouncer. */
+@SysUISingleton
+class KeyguardMediaKeyInteractor
+@Inject
+constructor(
+    private val telephonyInteractor: TelephonyInteractor,
+    private val audioRepository: AudioRepository,
+) : ExclusiveActivatable() {
+
+    /**
+     * Allows the media keys to work when the keyguard is showing. Forwards the relevant media keys
+     * to [AudioManager].
+     *
+     * @param event The key event
+     * @return whether the event was consumed as a media key.
+     */
+    fun processMediaKeyEvent(event: KeyEvent): Boolean {
+        if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) {
+            return false
+        }
+        val keyCode = event.keyCode
+        if (event.action == KeyEvent.ACTION_DOWN) {
+            when (keyCode) {
+                KeyEvent.KEYCODE_MEDIA_PLAY,
+                KeyEvent.KEYCODE_MEDIA_PAUSE,
+                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
+                    /* Suppress PLAY/PAUSE toggle when phone is ringing or
+                     * in-call to avoid music playback */
+                    // suppress key event
+                    return telephonyInteractor.isInCall.value
+                }
+
+                KeyEvent.KEYCODE_MUTE,
+                KeyEvent.KEYCODE_HEADSETHOOK,
+                KeyEvent.KEYCODE_MEDIA_STOP,
+                KeyEvent.KEYCODE_MEDIA_NEXT,
+                KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+                KeyEvent.KEYCODE_MEDIA_REWIND,
+                KeyEvent.KEYCODE_MEDIA_RECORD,
+                KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+                KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK -> {
+                    audioRepository.dispatchMediaKeyEvent(event)
+                    return true
+                }
+
+                KeyEvent.KEYCODE_VOLUME_UP,
+                KeyEvent.KEYCODE_VOLUME_DOWN,
+                KeyEvent.KEYCODE_VOLUME_MUTE -> return false
+            }
+        } else if (event.action == KeyEvent.ACTION_UP) {
+            when (keyCode) {
+                KeyEvent.KEYCODE_MUTE,
+                KeyEvent.KEYCODE_HEADSETHOOK,
+                KeyEvent.KEYCODE_MEDIA_PLAY,
+                KeyEvent.KEYCODE_MEDIA_PAUSE,
+                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
+                KeyEvent.KEYCODE_MEDIA_STOP,
+                KeyEvent.KEYCODE_MEDIA_NEXT,
+                KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+                KeyEvent.KEYCODE_MEDIA_REWIND,
+                KeyEvent.KEYCODE_MEDIA_RECORD,
+                KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+                KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK -> {
+                    audioRepository.dispatchMediaKeyEvent(event)
+                    return true
+                }
+            }
+        }
+        return false
+    }
+
+    override suspend fun onActivated(): Nothing {
+        // Collect to keep this flow hot for this interactor.
+        telephonyInteractor.isInCall.collect {}
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index cf747c8..34173a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
@@ -96,16 +97,19 @@
 
     /** Limit the max alpha for the scrim to allow for some transparency */
     val maxAlpha: Flow<Float> =
-        transitionInteractor
-            .isInTransition(
-                edge = Edge.create(Scenes.Gone, KeyguardState.AOD),
-                edgeWithoutSceneContainer = Edge.create(KeyguardState.GONE, KeyguardState.AOD),
+        anyOf(
+                transitionInteractor.isInTransition(
+                    edge = Edge.create(Scenes.Gone, KeyguardState.AOD),
+                    edgeWithoutSceneContainer = Edge.create(KeyguardState.GONE, KeyguardState.AOD),
+                ),
+                transitionInteractor.isInTransition(
+                    Edge.create(KeyguardState.OCCLUDED, KeyguardState.AOD)
+                ),
             )
             .flatMapLatest { isInTransition ->
-                // During GONE->AOD transitions, the home screen and wallpaper are still visible
-                // until
-                // WM is told to hide them, which occurs at the end of the animation. Use an opaque
-                // scrim until this transition is complete
+                // During transitions like GONE->AOD, surfaces like the launcher may be visible
+                // until WM is told to hide them, which occurs at the end of the animation. Use an
+                // opaque scrim until this transition is complete.
                 if (isInTransition) {
                     flowOf(1f)
                 } else {
@@ -149,7 +153,6 @@
             KeyguardState.DOZING -> false
             KeyguardState.AOD -> false
             KeyguardState.DREAMING -> true
-            KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
             KeyguardState.GLANCEABLE_HUB -> true
             KeyguardState.ALTERNATE_BOUNCER -> true
             KeyguardState.PRIMARY_BOUNCER -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 080ddfd..f0e79b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -41,12 +41,6 @@
      */
     DREAMING,
     /**
-     * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
-     * It is a special version of DREAMING state but not DOZING. The active dream will be windowless
-     * and hosted in the lockscreen.
-     */
-    DREAMING_LOCKSCREEN_HOSTED,
-    /**
      * The device has entered a special low-power mode within SystemUI, also called the Always-on
      * Display (AOD). A minimal UI is presented to show critical information. If the device is in
      * low-power mode without a UI, then it is DOZING.
@@ -125,7 +119,6 @@
             OFF,
             DOZING,
             DREAMING,
-            DREAMING_LOCKSCREEN_HOSTED,
             AOD,
             ALTERNATE_BOUNCER,
             OCCLUDED,
@@ -142,7 +135,6 @@
             OFF,
             DOZING,
             DREAMING,
-            DREAMING_LOCKSCREEN_HOSTED,
             AOD,
             ALTERNATE_BOUNCER,
             OCCLUDED,
@@ -166,7 +158,6 @@
                 OFF -> false
                 DOZING -> false
                 DREAMING -> false
-                DREAMING_LOCKSCREEN_HOSTED -> false
                 GLANCEABLE_HUB -> true
                 AOD -> false
                 ALTERNATE_BOUNCER -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
index 32757ce..741cc02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators.ALPHA_IN
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -43,14 +44,24 @@
                         }
                     }
                     launch("$TAG#viewModel.maxAlpha") {
-                        viewModel.maxAlpha.collect { alpha ->
+                        var animator: ValueAnimator? = null
+                        viewModel.maxAlpha.collect { (alpha, animate) ->
                             if (alpha != revealScrim.alpha) {
-                                ValueAnimator.ofFloat(revealScrim.alpha, alpha).apply {
-                                    duration = 400
-                                    addUpdateListener { animation ->
-                                        revealScrim.alpha = animation.getAnimatedValue() as Float
-                                    }
-                                    start()
+                                animator?.cancel()
+                                if (!animate) {
+                                    revealScrim.alpha = alpha
+                                } else {
+                                    animator =
+                                        ValueAnimator.ofFloat(revealScrim.alpha, alpha).apply {
+                                            startDelay = 333
+                                            duration = 733
+                                            interpolator = ALPHA_IN
+                                            addUpdateListener { animation ->
+                                                revealScrim.alpha =
+                                                    animation.getAnimatedValue() as Float
+                                            }
+                                            start()
+                                        }
                                 }
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 68244d8..4c667c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -114,7 +114,6 @@
                             keyguardTransitionInteractor.currentKeyguardState.replayCache.last()
                         ) {
                             KeyguardState.GLANCEABLE_HUB,
-                            KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
                             KeyguardState.GONE,
                             KeyguardState.OCCLUDED,
                             KeyguardState.OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index d3bb4f5..f5e0c81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -95,11 +95,10 @@
             .shareIn(scope, SharingStarted.WhileSubscribed())
             .onStart { emit(initialAlphaFromKeyguardState(transitionInteractor.getCurrentState())) }
     private val alphaMultiplierFromShadeExpansion: Flow<Float> =
-        combine(
-                showingAlternateBouncer,
+        combine(showingAlternateBouncer, shadeExpansion, qsProgress) {
+                showingAltBouncer,
                 shadeExpansion,
-                qsProgress,
-            ) { showingAltBouncer, shadeExpansion, qsProgress ->
+                qsProgress ->
                 val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
                 if (showingAltBouncer) {
                     1f
@@ -113,13 +112,9 @@
         combine(
             burnInInteractor.deviceEntryIconXOffset,
             burnInInteractor.deviceEntryIconYOffset,
-            burnInInteractor.udfpsProgress
+            burnInInteractor.udfpsProgress,
         ) { fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
-            BurnInOffsets(
-                fullyDozingBurnInX,
-                fullyDozingBurnInY,
-                fullyDozingBurnInProgress,
-            )
+            BurnInOffsets(fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress)
         }
 
     private val dozeAmount: Flow<Float> = transitionInteractor.transitionValue(KeyguardState.AOD)
@@ -129,22 +124,15 @@
             BurnInOffsets(
                 intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
                 intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
-                floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress)
+                floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress),
             )
         }
 
     val deviceEntryViewAlpha: Flow<Float> =
-        combine(
-                transitionAlpha,
-                alphaMultiplierFromShadeExpansion,
-            ) { alpha, alphaMultiplier ->
+        combine(transitionAlpha, alphaMultiplierFromShadeExpansion) { alpha, alphaMultiplier ->
                 alpha * alphaMultiplier
             }
-            .stateIn(
-                scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = 0f,
-            )
+            .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
 
     private fun initialAlphaFromKeyguardState(keyguardState: KeyguardState): Float {
         return when (keyguardState) {
@@ -155,11 +143,10 @@
             KeyguardState.GLANCEABLE_HUB,
             KeyguardState.GONE,
             KeyguardState.OCCLUDED,
-            KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
-            KeyguardState.UNDEFINED, -> 0f
+            KeyguardState.UNDEFINED -> 0f
             KeyguardState.AOD,
             KeyguardState.ALTERNATE_BOUNCER,
-            KeyguardState.LOCKSCREEN, -> 1f
+            KeyguardState.LOCKSCREEN -> 1f
         }
     }
 
@@ -171,7 +158,7 @@
                     combine(
                         transitionInteractor.startedKeyguardTransitionStep.sample(
                             shadeInteractor.isAnyFullyExpanded,
-                            ::Pair
+                            ::Pair,
                         ),
                         animatedBurnInOffsets,
                         nonAnimatedBurnInOffsets,
@@ -228,10 +215,9 @@
             }
 
     val iconType: Flow<DeviceEntryIconView.IconType> =
-        combine(
-            deviceEntryUdfpsInteractor.isListeningForUdfps,
-            isUnlocked,
-        ) { isListeningForUdfps, isUnlocked ->
+        combine(deviceEntryUdfpsInteractor.isListeningForUdfps, isUnlocked) {
+            isListeningForUdfps,
+            isUnlocked ->
             if (isListeningForUdfps) {
                 if (isUnlocked) {
                     // Don't show any UI until isUnlocked=false. This covers the case
@@ -250,10 +236,7 @@
     val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
 
     private val isInteractive: Flow<Boolean> =
-        combine(
-            iconType,
-            isUdfpsSupported,
-        ) { deviceEntryStatus, isUdfps ->
+        combine(iconType, isUdfpsSupported) { deviceEntryStatus, isUdfps ->
             when (deviceEntryStatus) {
                 DeviceEntryIconView.IconType.LOCK -> isUdfps
                 DeviceEntryIconView.IconType.UNLOCK -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
index af6cd16..6d1aefe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -21,6 +21,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 /**
  * Models UI state for the light reveal scrim, which is used during screen on and off animations to
@@ -32,7 +33,16 @@
 constructor(private val interactor: LightRevealScrimInteractor) {
     val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
     val revealAmount: Flow<Float> = interactor.revealAmount
-    val maxAlpha: Flow<Float> = interactor.maxAlpha
+
+    /** Max alpha for the scrim + whether to animate the change */
+    val maxAlpha: Flow<Pair<Float, Boolean>> =
+        interactor.maxAlpha.map { alpha ->
+            Pair(
+                alpha,
+                // Darken immediately if going to be fully opaque
+                if (alpha == 1f) false else true,
+            )
+        }
 
     fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) {
         interactor.setWallpaperSupportsAmbientMode(supportsAmbientMode)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 6db91ac..4071b13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -221,7 +221,7 @@
                 { notificationScrimClippingParams.params.top },
                 // Only allow scrolling when we are fully expanded. That way, we don't intercept
                 // swipes in lockscreen (when somehow QS is receiving touches).
-                { scrollState.canScrollForward && viewModel.isQsFullyExpanded },
+                { (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing },
             )
         frame.addView(
             composeView,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 71fa0ac..7b25939 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -77,25 +77,24 @@
     colors: TileColors,
     squishiness: () -> Float,
     accessibilityUiState: AccessibilityUiState? = null,
-    toggleClickSupported: Boolean = false,
     iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
-    onClick: () -> Unit = {},
-    onLongClick: () -> Unit = {},
+    toggleClick: (() -> Unit)? = null,
+    onLongClick: (() -> Unit)? = null,
 ) {
     Row(
         verticalAlignment = Alignment.CenterVertically,
         horizontalArrangement = tileHorizontalArrangement(),
     ) {
         // Icon
-        val longPressLabel = longPressLabel()
+        val longPressLabel = longPressLabel().takeIf { onLongClick != null }
         Box(
             modifier =
-                Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
+                Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
                     Modifier.clip(iconShape)
                         .verticalSquish(squishiness)
                         .background(colors.iconBackground, { 1f })
                         .combinedClickable(
-                            onClick = onClick,
+                            onClick = toggleClick!!,
                             onLongClick = onLongClick,
                             onLongClickLabel = longPressLabel,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index d2ec958..b581c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -202,14 +202,17 @@
         topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
     ) { innerPadding ->
         CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+            val scrollState = rememberScrollState()
+            LaunchedEffect(listState.dragInProgress) {
+                if (listState.dragInProgress) {
+                    scrollState.animateScrollTo(0)
+                }
+            }
+
             Column(
                 verticalArrangement =
                     spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
-                modifier =
-                    modifier
-                        .fillMaxSize()
-                        .verticalScroll(rememberScrollState())
-                        .padding(innerPadding),
+                modifier = modifier.fillMaxSize().verticalScroll(scrollState).padding(innerPadding),
             ) {
                 AnimatedContent(
                     targetState = listState.dragInProgress,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 52d5261..5f28fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -160,19 +160,18 @@
                 )
             } else {
                 val iconShape = TileDefaults.animateIconShape(uiState.state)
+                val secondaryClick: (() -> Unit)? =
+                    { tile.onSecondaryClick() }.takeIf { uiState.handlesSecondaryClick }
+                val longClick: (() -> Unit)? =
+                    { tile.onLongClick(expandable) }.takeIf { uiState.handlesLongClick }
                 LargeTileContent(
                     label = uiState.label,
                     secondaryLabel = uiState.secondaryLabel,
                     icon = icon,
                     colors = colors,
                     iconShape = iconShape,
-                    toggleClickSupported = state.handlesSecondaryClick,
-                    onClick = {
-                        if (state.handlesSecondaryClick) {
-                            tile.onSecondaryClick()
-                        }
-                    },
-                    onLongClick = { tile.onLongClick(expandable) },
+                    toggleClick = secondaryClick,
+                    onLongClick = longClick,
                     accessibilityUiState = uiState.accessibilityUiState,
                     squishiness = squishiness,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index aa42080..56675e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -33,6 +33,7 @@
     val label: String,
     val secondaryLabel: String,
     val state: Int,
+    val handlesLongClick: Boolean,
     val handlesSecondaryClick: Boolean,
     val icon: Supplier<QSTile.Icon?>,
     val accessibilityUiState: AccessibilityUiState,
@@ -86,6 +87,7 @@
         label = label?.toString() ?: "",
         secondaryLabel = secondaryLabel?.toString() ?: "",
         state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state,
+        handlesLongClick = handlesLongClick,
         handlesSecondaryClick = handlesSecondaryClick,
         icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
         AccessibilityUiState(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
deleted file mode 100644
index f73d204..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
+++ /dev/null
@@ -1,51 +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.screenshot
-
-import android.annotation.UserIdInt
-import android.content.ComponentName
-import android.graphics.Rect
-import android.os.UserHandle
-import android.view.Display
-
-/**
- * Provides policy decision-making information to screenshot request handling.
- */
-interface ScreenshotPolicy {
-
-    /** @return true if the user is a managed profile (a.k.a. work profile) */
-    suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean
-
-    /**
-     * Requests information about the owner of display content which occupies a majority of the
-     * screenshot and/or has most recently been interacted with at the time the screenshot was
-     * requested.
-     *
-     * @param displayId the id of the display to inspect
-     * @return content info for the primary content on the display
-     */
-    suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo
-
-    data class DisplayContentInfo(
-        val component: ComponentName,
-        val bounds: Rect,
-        val user: UserHandle,
-        val taskId: Int,
-    )
-
-    fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
deleted file mode 100644
index 21a7310..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ /dev/null
@@ -1,189 +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.screenshot
-
-import android.annotation.UserIdInt
-import android.app.ActivityTaskManager
-import android.app.ActivityTaskManager.RootTaskInfo
-import android.app.IActivityTaskManager
-import android.app.WindowConfiguration
-import android.app.WindowConfiguration.activityTypeToString
-import android.app.WindowConfiguration.windowingModeToString
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.graphics.Rect
-import android.os.Process
-import android.os.RemoteException
-import android.os.UserHandle
-import android.os.UserManager
-import android.util.Log
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.infra.ServiceConnector
-import com.android.systemui.SystemUIService
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-import java.util.Arrays
-import javax.inject.Inject
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.withContext
-
-@SysUISingleton
-internal open class ScreenshotPolicyImpl @Inject constructor(
-    context: Context,
-    private val userMgr: UserManager,
-    private val atmService: IActivityTaskManager,
-    @Background val bgDispatcher: CoroutineDispatcher,
-    private val displayTracker: DisplayTracker
-) : ScreenshotPolicy {
-
-    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
-        ServiceConnector.Impl(
-            context,
-            Intent(context, ScreenshotProxyService::class.java),
-            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
-            context.userId,
-            IScreenshotProxy.Stub::asInterface
-        )
-
-    override fun getDefaultDisplayId(): Int {
-        return displayTracker.defaultDisplayId
-    }
-
-    override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
-        val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
-        Log.d(TAG, "isManagedProfile: $managed")
-        return managed
-    }
-
-    private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
-        if (DEBUG) {
-            debugLogRootTaskInfo(info)
-        }
-        return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
-            info.isVisible &&
-            info.isRunning &&
-            info.numActivities > 0 &&
-            info.topActivity != null &&
-            info.childTaskIds.isNotEmpty()
-    }
-
-    /**
-     * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
-     * display. If no task is visible or the top task is covered by a system window, the info
-     * reported will reference a SystemUI component instead.
-     */
-    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
-        // Determine if the notification shade is expanded. If so, task windows are not
-        // visible behind it, so the screenshot should instead be associated with SystemUI.
-        if (isNotificationShadeExpanded()) {
-            return systemUiContent
-        }
-
-        val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
-
-        // If no visible task is located, then report SystemUI as the foreground content
-        val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
-        return target.toDisplayContentInfo()
-    }
-
-    private fun debugLogRootTaskInfo(info: RootTaskInfo) {
-        Log.d(TAG, "RootTaskInfo={" +
-                "taskId=${info.taskId} " +
-                "parentTaskId=${info.parentTaskId} " +
-                "position=${info.position} " +
-                "positionInParent=${info.positionInParent} " +
-                "isVisible=${info.isVisible()} " +
-                "visible=${info.visible} " +
-                "isFocused=${info.isFocused} " +
-                "isSleeping=${info.isSleeping} " +
-                "isRunning=${info.isRunning} " +
-                "windowMode=${windowingModeToString(info.windowingMode)} " +
-                "activityType=${activityTypeToString(info.activityType)} " +
-                "topActivity=${info.topActivity} " +
-                "topActivityInfo=${info.topActivityInfo} " +
-                "numActivities=${info.numActivities} " +
-                "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
-                "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
-                "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
-                "childTaskNames=${Arrays.toString(info.childTaskNames)}" +
-                "}"
-        )
-
-        for (j in 0 until info.childTaskIds.size) {
-            Log.d(TAG, "    *** [$j] ******")
-            Log.d(TAG, "        ***  childTaskIds[$j]: ${info.childTaskIds[j]}")
-            Log.d(TAG, "        ***  childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
-            Log.d(TAG, "        ***  childTaskBounds[$j]: ${info.childTaskBounds[j]}")
-            Log.d(TAG, "        ***  childTaskNames[$j]: ${info.childTaskNames[j]}")
-        }
-    }
-
-    @VisibleForTesting
-    open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
-        withContext(bgDispatcher) {
-            try {
-                atmService.getAllRootTaskInfosOnDisplay(displayId)
-            } catch (e: RemoteException) {
-                Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
-                listOf()
-            }
-        }
-
-    @VisibleForTesting
-    open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
-        proxyConnector
-            .postForResult { it.isNotificationShadeExpanded }
-            .whenComplete { expanded, error ->
-                if (error != null) {
-                    Log.e(TAG, "isNotificationShadeExpanded", error)
-                }
-                k.resume(expanded ?: false)
-            }
-    }
-
-    @VisibleForTesting
-    internal val systemUiContent =
-        DisplayContentInfo(
-            ComponentName(context, SystemUIService::class.java),
-            Rect(),
-            Process.myUserHandle(),
-            ActivityTaskManager.INVALID_TASK_ID
-        )
-}
-
-private const val TAG: String = "ScreenshotPolicyImpl"
-private const val DEBUG: Boolean = false
-
-@VisibleForTesting
-internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo {
-    val topActivity: ComponentName = topActivity ?: error("should not be null")
-    val topChildTask = childTaskIds.size - 1
-    val childTaskId = childTaskIds[topChildTask]
-    val childTaskUserId = childTaskUserIds[topChildTask]
-    val childTaskBounds = childTaskBounds[topChildTask]
-
-    return DisplayContentInfo(
-        topActivity,
-        childTaskBounds,
-        UserHandle.of(childTaskUserId),
-        childTaskId)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 254dde4..90695fa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -27,8 +27,6 @@
 import com.android.systemui.screenshot.InteractiveScreenshotHandler;
 import com.android.systemui.screenshot.LegacyScreenshotController;
 import com.android.systemui.screenshot.ScreenshotController;
-import com.android.systemui.screenshot.ScreenshotPolicy;
-import com.android.systemui.screenshot.ScreenshotPolicyImpl;
 import com.android.systemui.screenshot.ScreenshotSoundController;
 import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
 import com.android.systemui.screenshot.ScreenshotSoundProvider;
@@ -66,9 +64,6 @@
             TakeScreenshotExecutorImpl impl);
 
     @Binds
-    abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
-
-    @Binds
     abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
index dae8512..258befe 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
@@ -18,15 +18,19 @@
 
 import android.content.Context
 
-/**
- * Implemented by [UserTrackerImpl].
- */
+/** Implemented by [UserTrackerImpl]. */
 interface UserContextProvider {
+    /**
+     * provides system context, not current user context.
+     *
+     * To get current user context use [createCurrentUserContext] passing [userContext] as context
+     */
     val userContext: Context
 
     /**
      * Creates the {@code context} with the current user.
+     *
      * @see Context#createContextAsUser(UserHandle, int)
      */
     fun createCurrentUserContext(context: Context): Context
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 75165cb..8703f68 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -40,6 +40,8 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.time.SystemClock;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import javax.inject.Inject;
 
 /**
@@ -283,12 +285,14 @@
         private final VibratorHelper mVibratorHelper;
         private final SystemClock mSystemClock;
         private final ActivityStarter mActivityStarter;
+        private final MSDLPlayer mMSDLPlayer;
 
         @Inject
         public Factory(
                 FalsingManager falsingManager,
                 UiEventLogger uiEventLogger,
                 VibratorHelper vibratorHelper,
+                MSDLPlayer msdlPlayer,
                 SystemClock clock,
                 ActivityStarter activityStarter
         ) {
@@ -297,6 +301,7 @@
             mVibratorHelper = vibratorHelper;
             mSystemClock = clock;
             mActivityStarter = activityStarter;
+            mMSDLPlayer = msdlPlayer;
         }
 
         /**
@@ -314,6 +319,7 @@
                     .inflate(layout, viewRoot, false);
             SeekbarHapticPlugin plugin = new SeekbarHapticPlugin(
                     mVibratorHelper,
+                    mMSDLPlayer,
                     mSystemClock);
             HapticSliderViewBinder.bind(viewRoot, plugin);
             return new BrightnessSliderController(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
deleted file mode 100644
index 45fc68a..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade
-
-import android.os.PowerManager
-import android.view.GestureDetector
-import android.view.MotionEvent
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-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.power.domain.interactor.PowerInteractor
-import com.android.systemui.statusbar.StatusBarState
-import javax.inject.Inject
-
-/**
- * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
- * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
- *
- * Touches go through the [NotificationShadeWindowViewController].
- */
-@SysUISingleton
-class LockscreenHostedDreamGestureListener
-@Inject
-constructor(
-    private val falsingManager: FalsingManager,
-    private val powerInteractor: PowerInteractor,
-    private val statusBarStateController: StatusBarStateController,
-    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
-    private val keyguardRepository: KeyguardRepository,
-    private val shadeLogger: ShadeLogger,
-) : GestureDetector.SimpleOnGestureListener() {
-    private val TAG = this::class.simpleName
-
-    override fun onSingleTapUp(e: MotionEvent): Boolean {
-        if (shouldHandleMotionEvent()) {
-            if (!falsingManager.isFalseTap(LOW_PENALTY)) {
-                shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
-                powerInteractor.wakeUpIfDreaming(
-                    "DREAMING_SINGLE_TAP",
-                    PowerManager.WAKE_REASON_TAP
-                )
-            } else {
-                shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
-            }
-            return true
-        }
-        return false
-    }
-
-    private fun shouldHandleMotionEvent(): Boolean {
-        return keyguardRepository.isActiveDreamLockscreenHosted.value &&
-            statusBarStateController.state == StatusBarState.KEYGUARD &&
-            !primaryBouncerInteractor.isBouncerShowing()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAware.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAware.kt
new file mode 100644
index 0000000..111d335
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAware.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import javax.inject.Qualifier
+
+/**
+ * Qualifies classes that provide display-specific info for shade window components.
+ *
+ * The Shade window can be moved between displays with different characteristics (e.g., density,
+ * size). This annotation ensures that components within the shade window use the correct context
+ * and resources for the display they are currently on.
+ *
+ * Classes annotated with `@ShadeDisplayAware` (e.g., 'Context`, `Resources`, `LayoutInflater`,
+ * `ConfigurationController`) will be dynamically updated to reflect the current display's
+ * configuration. This ensures consistent rendering even when the shade window is moved to an
+ * external display.
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ShadeDisplayAware
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f99d8f1..520cbf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -214,7 +214,7 @@
     private boolean mEnableBatteryDefender;
     private boolean mIncompatibleCharger;
     private int mChargingWattage;
-    private int mBatteryLevel;
+    private int mBatteryLevel = -1;
     private boolean mBatteryPresent = true;
     protected long mChargingTimeRemaining;
     private Pair<String, BiometricSourceType> mBiometricErrorMessageToShowOnScreenOn;
@@ -1032,12 +1032,16 @@
             } else if (!TextUtils.isEmpty(mTransientIndication)) {
                 newIndication = mTransientIndication;
             } else if (!mBatteryPresent) {
-                // If there is no battery detected, hide the indication and bail
+                // If there is no battery detected, hide the indication area and bail
                 mIndicationArea.setVisibility(GONE);
                 return;
             } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
                 useMisalignmentColor = true;
                 newIndication = mAlignmentIndication;
+            } else if (mBatteryLevel == -1) {
+                // If the battery level is not initialized, hide the indication area
+                mIndicationArea.setVisibility(GONE);
+                return;
             } else if (mPowerPluggedIn || mEnableBatteryDefender) {
                 newIndication = computePowerIndication();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 2930de2..0eef8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -44,8 +44,12 @@
 import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 import com.android.systemui.util.leak.RotationUtils.Rotation
+import dagger.Module
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import java.util.concurrent.Executor
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
@@ -63,26 +67,57 @@
  * NOTE: any operation that modifies views directly must run on the provided executor, because these
  * views are owned by ScreenDecorations and it runs in its own thread
  */
-@SysUISingleton
-open class PrivacyDotViewController
-@Inject
+interface PrivacyDotViewController {
+
+    // Only can be modified on @UiThread
+    var currentViewState: ViewState
+
+    var showingListener: ShowingListener?
+
+    fun setUiExecutor(e: DelayableExecutor)
+
+    fun getUiExecutor(): DelayableExecutor?
+
+    @UiThread fun setNewRotation(rot: Int)
+
+    @UiThread fun hideDotView(dot: View, animate: Boolean)
+
+    @UiThread fun showDotView(dot: View, animate: Boolean)
+
+    // Update the gravity and margins of the privacy views
+    @UiThread fun updateRotations(rotation: Int, paddingTop: Int)
+
+    @UiThread fun setCornerSizes(state: ViewState)
+
+    fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View)
+
+    @UiThread fun updateDotView(state: ViewState)
+
+    interface ShowingListener {
+        fun onPrivacyDotShown(v: View?)
+
+        fun onPrivacyDotHidden(v: View?)
+    }
+}
+
+open class PrivacyDotViewControllerImpl
+@AssistedInject
 constructor(
     @Main private val mainExecutor: Executor,
-    @Application scope: CoroutineScope,
+    @Assisted scope: CoroutineScope,
     private val stateController: StatusBarStateController,
-    private val configurationController: ConfigurationController,
-    private val contentInsetsProvider: StatusBarContentInsetsProvider,
+    @Assisted private val configurationController: ConfigurationController,
+    @Assisted private val contentInsetsProvider: StatusBarContentInsetsProvider,
     private val animationScheduler: SystemStatusAnimationScheduler,
-    shadeInteractor: ShadeInteractor?
-) {
+    shadeInteractor: ShadeInteractor?,
+) : PrivacyDotViewController {
     private lateinit var tl: View
     private lateinit var tr: View
     private lateinit var bl: View
     private lateinit var br: View
 
     // Only can be modified on @UiThread
-    var currentViewState: ViewState = ViewState()
-        get() = field
+    override var currentViewState: ViewState = ViewState()
 
     @GuardedBy("lock")
     private var nextViewState: ViewState = currentViewState.copy()
@@ -100,11 +135,7 @@
     private val views: Sequence<View>
         get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
 
-    var showingListener: ShowingListener? = null
-        set(value) {
-            field = value
-        }
-        get() = field
+    override var showingListener: PrivacyDotViewController.ShowingListener? = null
 
     init {
         contentInsetsProvider.addCallback(
@@ -153,16 +184,16 @@
         }
     }
 
-    fun setUiExecutor(e: DelayableExecutor) {
+    override fun setUiExecutor(e: DelayableExecutor) {
         uiExecutor = e
     }
 
-    fun getUiExecutor(): DelayableExecutor? {
+    override fun getUiExecutor(): DelayableExecutor? {
         return uiExecutor
     }
 
     @UiThread
-    fun setNewRotation(rot: Int) {
+    override fun setNewRotation(rot: Int) {
         dlog("updateRotation: $rot")
 
         val isRtl: Boolean
@@ -187,13 +218,13 @@
                     rotation = rot,
                     paddingTop = paddingTop,
                     designatedCorner = newCorner,
-                    cornerIndex = index
+                    cornerIndex = index,
                 )
         }
     }
 
     @UiThread
-    fun hideDotView(dot: View, animate: Boolean) {
+    override fun hideDotView(dot: View, animate: Boolean) {
         dot.clearAnimation()
         if (animate) {
             dot.animate()
@@ -212,7 +243,7 @@
     }
 
     @UiThread
-    fun showDotView(dot: View, animate: Boolean) {
+    override fun showDotView(dot: View, animate: Boolean) {
         dot.clearAnimation()
         if (animate) {
             dot.visibility = View.VISIBLE
@@ -229,9 +260,8 @@
         showingListener?.onPrivacyDotShown(dot)
     }
 
-    // Update the gravity and margins of the privacy views
     @UiThread
-    open fun updateRotations(rotation: Int, paddingTop: Int) {
+    override fun updateRotations(rotation: Int, paddingTop: Int) {
         // To keep a view in the corner, its gravity is always the description of its current corner
         // Therefore, just figure out which view is in which corner. This turns out to be something
         // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -262,7 +292,7 @@
     }
 
     @UiThread
-    open fun setCornerSizes(state: ViewState) {
+    override fun setCornerSizes(state: ViewState) {
         // StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
@@ -415,7 +445,7 @@
         }
     }
 
-    fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+    override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
         if (
             this::tl.isInitialized &&
                 this::tr.isInitialized &&
@@ -457,7 +487,7 @@
                     landscapeRect = right,
                     upsideDownRect = bottom,
                     paddingTop = paddingTop,
-                    layoutRtl = rtl
+                    layoutRtl = rtl,
                 )
         }
     }
@@ -533,7 +563,7 @@
     }
 
     @UiThread
-    open fun updateDotView(state: ViewState) {
+    override fun updateDotView(state: ViewState) {
         val shouldShow = state.shouldShowDot()
         if (shouldShow != currentViewState.shouldShowDot()) {
             if (shouldShow && state.designatedCorner != null) {
@@ -553,7 +583,7 @@
                     nextViewState =
                         nextViewState.copy(
                             systemPrivacyEventIsActive = true,
-                            contentDescription = contentDescr
+                            contentDescription = contentDescr,
                         )
                 }
 
@@ -595,15 +625,18 @@
                     seascapeRect = rects[0],
                     portraitRect = rects[1],
                     landscapeRect = rects[2],
-                    upsideDownRect = rects[3]
+                    upsideDownRect = rects[3],
                 )
         }
     }
 
-    interface ShowingListener {
-        fun onPrivacyDotShown(v: View?)
-
-        fun onPrivacyDotHidden(v: View?)
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            scope: CoroutineScope,
+            configurationController: ConfigurationController,
+            contentInsetsProvider: StatusBarContentInsetsProvider,
+        ): PrivacyDotViewControllerImpl
     }
 }
 
@@ -662,7 +695,7 @@
     val paddingTop: Int = 0,
     val cornerIndex: Int = -1,
     val designatedCorner: View? = null,
-    val contentDescription: String? = null
+    val contentDescription: String? = null,
 ) {
     fun shouldShowDot(): Boolean {
         return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded
@@ -687,3 +720,18 @@
         }
     }
 }
+
+@Module
+object PrivacyDotViewControllerModule {
+
+    @Provides
+    @SysUISingleton
+    fun controller(
+        factory: PrivacyDotViewControllerImpl.Factory,
+        @Application scope: CoroutineScope,
+        configurationController: ConfigurationController,
+        contentInsetsProvider: StatusBarContentInsetsProvider,
+    ): PrivacyDotViewController {
+        return factory.create(scope, configurationController, contentInsetsProvider)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
index 231a0b0..9fe4a54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
@@ -32,13 +32,13 @@
 interface NotificationActivityStarter {
 
     /** Called when the user clicks on the notification bubble icon. */
-    fun onNotificationBubbleIconClicked(entry: NotificationEntry?)
+    fun onNotificationBubbleIconClicked(entry: NotificationEntry)
 
     /** Called when the user clicks on the surface of a notification. */
-    fun onNotificationClicked(entry: NotificationEntry?, row: ExpandableNotificationRow?)
+    fun onNotificationClicked(entry: NotificationEntry, row: ExpandableNotificationRow)
 
     /** Called when the user clicks on a button in the notification guts which fires an intent. */
-    fun startNotificationGutsIntent(intent: Intent?, appUid: Int, row: ExpandableNotificationRow?)
+    fun startNotificationGutsIntent(intent: Intent, appUid: Int, row: ExpandableNotificationRow)
 
     /**
      * Called when the user clicks "Manage" or "History" in the Shade. Prefer using
@@ -56,7 +56,7 @@
     fun startSettingsIntent(view: View, intentInfo: SettingsIntent)
 
     /** Called when the user succeed to drop notification to proper target view. */
-    fun onDragSuccess(entry: NotificationEntry?)
+    fun onDragSuccess(entry: NotificationEntry)
 
     val isCollapsingToShowActivityOverLockscreen: Boolean
         get() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
index 2ee1dffd..9580016 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
@@ -35,6 +35,9 @@
  * This cache is safe for multithreaded usage, and is recommended for objects that take a while to
  * resolve (such as drawables, or things that require binder calls). As such, [getOrFetch] is
  * recommended to be run on a background thread, while [purge] can be done from any thread.
+ *
+ * Important: This cache does NOT have a maximum size, cleaning it up (via [purge]) is the
+ * responsibility of the caller, to avoid keeping things in memory unnecessarily.
  */
 @SuppressLint("DumpableNotRegistered") // this will be dumped by container classes
 class NotifCollectionCache<V>(
@@ -151,7 +154,7 @@
      * purge((c));    // deletes a from the cache and marks b for deletion, etc.
      * ```
      */
-    fun purge(wantedKeys: List<String>) {
+    fun purge(wantedKeys: Collection<String>) {
         for ((key, entry) in cache) {
             if (key in wantedKeys) {
                 entry.resetLives()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 9b075a6..f75163d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -27,6 +27,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -49,13 +50,18 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -104,6 +110,8 @@
     /** How long we can delay a group while waiting for all children to inflate */
     private final long mMaxGroupInflationDelay;
     private final BindEventManagerImpl mBindEventManager;
+    private final AppIconProvider mAppIconProvider;
+    private final NotificationIconStyleProvider mNotificationIconStyleProvider;
 
     @Inject
     public PreparationCoordinator(
@@ -113,7 +121,9 @@
             NotifViewBarn viewBarn,
             NotifUiAdjustmentProvider adjustmentProvider,
             IStatusBarService service,
-            BindEventManagerImpl bindEventManager) {
+            BindEventManagerImpl bindEventManager,
+            AppIconProvider appIconProvider,
+            NotificationIconStyleProvider notificationIconStyleProvider) {
         this(
                 logger,
                 notifInflater,
@@ -122,6 +132,8 @@
                 adjustmentProvider,
                 service,
                 bindEventManager,
+                appIconProvider,
+                notificationIconStyleProvider,
                 CHILD_BIND_CUTOFF,
                 MAX_GROUP_INFLATION_DELAY);
     }
@@ -135,6 +147,8 @@
             NotifUiAdjustmentProvider adjustmentProvider,
             IStatusBarService service,
             BindEventManagerImpl bindEventManager,
+            AppIconProvider appIconProvider,
+            NotificationIconStyleProvider notificationIconStyleProvider,
             int childBindCutoff,
             long maxGroupInflationDelay) {
         mLogger = logger;
@@ -146,6 +160,8 @@
         mChildBindCutoff = childBindCutoff;
         mMaxGroupInflationDelay = maxGroupInflationDelay;
         mBindEventManager = bindEventManager;
+        mAppIconProvider = appIconProvider;
+        mNotificationIconStyleProvider = notificationIconStyleProvider;
     }
 
     @Override
@@ -155,6 +171,9 @@
                 () -> mNotifInflatingFilter.invalidateList("adjustmentProviderChanged"));
 
         pipeline.addCollectionListener(mNotifCollectionListener);
+        if (android.app.Flags.notificationsRedesignAppIcons()) {
+            pipeline.addOnBeforeTransformGroupsListener(this::purgeCaches);
+        }
         // Inflate after grouping/sorting since that affects what views to inflate.
         pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews);
         pipeline.addFinalizeFilter(mNotifInflationErrorFilter);
@@ -260,6 +279,29 @@
         }
     };
 
+    private void purgeCaches(Collection<ListEntry> entries) {
+        Set<String> wantedPackages = getPackages(entries);
+        mAppIconProvider.purgeCache(wantedPackages);
+        mNotificationIconStyleProvider.purgeCache(wantedPackages);
+    }
+
+    /**
+     * Get all app packages present in {@param entries}.
+     */
+    private static @NonNull Set<String> getPackages(Collection<ListEntry> entries) {
+        Set<String> packages = new HashSet<>();
+        for (ListEntry entry : entries) {
+            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+            if (notificationEntry == null) {
+                Log.wtf(TAG, "notification entry " + entry.getKey()
+                        + " has no representative entry");
+                continue;
+            }
+            packages.add(notificationEntry.getSbn().getPackageName());
+        }
+        return packages;
+    }
+
     private void inflateAllRequiredViews(List<ListEntry> entries) {
         for (int i = 0, size = entries.size(); i < size; i++) {
             ListEntry entry = entries.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 6edffc0..8660cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,9 +29,11 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
@@ -44,7 +46,6 @@
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.BooleanFlowOperators;
 import com.android.systemui.util.kotlin.JavaAdapter;
@@ -77,7 +78,6 @@
     private final CommunalSceneInteractor mCommunalSceneInteractor;
     private final ShadeInteractor mShadeInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    private final KeyguardStateController mKeyguardStateController;
     private final VisualStabilityCoordinatorLogger mLogger;
 
     private boolean mSleepy = true;
@@ -120,7 +120,6 @@
             CommunalSceneInteractor communalSceneInteractor,
             ShadeInteractor shadeInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            KeyguardStateController keyguardStateController,
             VisualStabilityCoordinatorLogger logger) {
         mHeadsUpManager = headsUpManager;
         mShadeAnimationInteractor = shadeAnimationInteractor;
@@ -134,7 +133,6 @@
         mCommunalSceneInteractor = communalSceneInteractor;
         mShadeInteractor = shadeInteractor;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
-        mKeyguardStateController = keyguardStateController;
         mLogger = logger;
 
         dumpManager.registerDumpable(this);
@@ -164,23 +162,17 @@
                             KeyguardState.LOCKSCREEN),
                     this::onLockscreenKeyguardStateTransitionValueChanged);
         }
-
         if (Flags.checkLockscreenGoneTransition()) {
-            mKeyguardStateController.addCallback(mKeyguardFadeAwayAnimationCallback);
+            mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+                            Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
+                            Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
+                    this::onLockscreenInGoneTransitionChanged);
         }
 
+
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
 
-    final KeyguardStateController.Callback mKeyguardFadeAwayAnimationCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardFadingAwayChanged() {
-                    onLockscreenInGoneTransitionChanged(
-                            mKeyguardStateController.isKeyguardFadingAway());
-                }
-            };
-
     // TODO(b/203826051): Ensure stability manager can allow reordering off-screen
     //  HUNs to the top of the shade
     private final NotifStabilityManager mNotifStabilityManager =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 24b5cf1a..0ddf9f72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row.icon
 
+import android.annotation.WorkerThread
 import android.app.ActivityManager
 import android.app.Flags
 import android.content.Context
@@ -27,20 +28,45 @@
 import android.util.Log
 import com.android.internal.R
 import com.android.launcher3.icons.BaseIconFactory
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
 import dagger.Module
 import dagger.Provides
+import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
 
 /** A provider used to cache and fetch app icons used by notifications. */
 interface AppIconProvider {
+    /**
+     * Loads the icon corresponding to [packageName] into cache, or fetches it from there if already
+     * present. This should only be called from the background.
+     */
     @Throws(NameNotFoundException::class)
+    @WorkerThread
     fun getOrFetchAppIcon(packageName: String, context: Context): Drawable
+
+    /**
+     * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
+     * still not needed on the next call of this method (made after a timeout of 1s, in case they
+     * happen more frequently than that), they will be purged. This can be done from any thread.
+     */
+    fun purgeCache(wantedPackages: Collection<String>)
 }
 
 @SysUISingleton
-class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider {
+class AppIconProviderImpl
+@Inject
+constructor(private val sysuiContext: Context, dumpManager: DumpManager) :
+    AppIconProvider, Dumpable {
+    init {
+        dumpManager.registerNormalDumpable(TAG, this)
+    }
+
     private val iconFactory: BaseIconFactory
         get() {
             val isLowRam = ActivityManager.isLowRamDeviceStatic()
@@ -53,13 +79,42 @@
             return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize)
         }
 
+    private val cache = NotifCollectionCache<Drawable>()
+
     override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable {
+        return cache.getOrFetch(packageName) { fetchAppIcon(packageName, context) }
+    }
+
+    @WorkerThread
+    private fun fetchAppIcon(packageName: String, context: Context): BitmapDrawable {
         val icon = context.packageManager.getApplicationIcon(packageName)
         return BitmapDrawable(
             context.resources,
             iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE),
         )
     }
+
+    override fun purgeCache(wantedPackages: Collection<String>) {
+        cache.purge(wantedPackages)
+    }
+
+    override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
+        val pw = pwOrig.asIndenting()
+
+        pw.println("cache information:")
+        pw.withIncreasedIndent { cache.dump(pw, args) }
+
+        val iconFactory = iconFactory
+        pw.println("icon factory information:")
+        pw.withIncreasedIndent {
+            pw.println("fullResIconDpi = ${iconFactory.fullResIconDpi}")
+            pw.println("iconSize = ${iconFactory.iconBitmapSize}")
+        }
+    }
+
+    companion object {
+        const val TAG = "AppIconProviderImpl"
+    }
 }
 
 class NoOpIconProvider : AppIconProvider {
@@ -71,6 +126,10 @@
         Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
         return ColorDrawable(Color.WHITE)
     }
+
+    override fun purgeCache(wantedPackages: Collection<String>) {
+        Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
index 165c1a7..35e38c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
@@ -22,9 +22,15 @@
 import android.content.pm.ApplicationInfo
 import android.service.notification.StatusBarNotification
 import android.util.Log
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
 import dagger.Module
 import dagger.Provides
+import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -33,15 +39,35 @@
  * notifications.
  */
 interface NotificationIconStyleProvider {
+    /**
+     * Determines whether the [notification] should display the app icon instead of the small icon.
+     * This can result in a binder call, and therefore should only be called from the background.
+     */
     @WorkerThread
     fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean
+
+    /**
+     * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
+     * still not needed on the next call of this method (made after a timeout of 1s, in case they
+     * happen more frequently than that), they will be purged. This can be done from any thread.
+     */
+    fun purgeCache(wantedPackages: Collection<String>)
 }
 
 @SysUISingleton
-class NotificationIconStyleProviderImpl @Inject constructor() : NotificationIconStyleProvider {
+class NotificationIconStyleProviderImpl @Inject constructor(dumpManager: DumpManager) :
+    NotificationIconStyleProvider, Dumpable {
+    init {
+        dumpManager.registerNormalDumpable(TAG, this)
+    }
+
+    private val cache = NotifCollectionCache<Boolean>()
+
     override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean {
         val packageContext = notification.getPackageContext(context)
-        return !belongsToHeadlessSystemApp(packageContext)
+        return cache.getOrFetch(notification.packageName) {
+            !belongsToHeadlessSystemApp(packageContext)
+        }
     }
 
     @WorkerThread
@@ -62,6 +88,20 @@
             return false
         }
     }
+
+    override fun purgeCache(wantedPackages: Collection<String>) {
+        cache.purge(wantedPackages)
+    }
+
+    override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
+        val pw = pwOrig.asIndenting()
+        pw.println("cache information:")
+        pw.withIncreasedIndent { cache.dump(pw, args) }
+    }
+
+    companion object {
+        const val TAG = "NotificationIconStyleProviderImpl"
+    }
 }
 
 class NoOpIconStyleProvider : NotificationIconStyleProvider {
@@ -73,6 +113,10 @@
         Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.")
         return true
     }
+
+    override fun purgeCache(wantedPackages: Collection<String>) {
+        Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.")
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index a8c823c..858cac1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -29,9 +29,8 @@
 
 class ConfigurationControllerImpl
 @AssistedInject
-constructor(
-    @Assisted private val context: Context,
-) : ConfigurationController, StatusBarConfigurationController {
+constructor(@Assisted private val context: Context) :
+    ConfigurationController, StatusBarConfigurationController {
 
     private val listeners: MutableList<ConfigurationListener> = ArrayList()
     private val lastConfig = Configuration()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
new file mode 100644
index 0000000..3fd46fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.phone
+
+import android.content.res.Configuration
+
+/**
+ * Used to forward a configuration change to other components.
+ *
+ * This is commonly used to propagate configs to [ConfigurationController]. Note that there could be
+ * different configuration forwarder, for example each display, window or group of classes (e.g.
+ * shade window classes).
+ */
+interface ConfigurationForwarder {
+    /** Should be called when a new configuration is received. */
+    fun onConfigurationChanged(newConfiguration: Configuration)
+}
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 74c6e72..f7fea7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1535,6 +1535,7 @@
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
+        ComposeBouncerFlags.assertInLegacyMode();
         return mPrimaryBouncerView.getDelegate() != null
                 && mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 93db2db..af98311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -20,7 +20,6 @@
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 
 import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
-import static com.android.systemui.util.kotlin.NullabilityKt.expectNotNull;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -231,8 +230,7 @@
      * @param entry notification that bubble icon was clicked
      */
     @Override
-    public void onNotificationBubbleIconClicked(NotificationEntry entry) {
-        expectNotNull(TAG, "entry", entry);
+    public void onNotificationBubbleIconClicked(@NonNull NotificationEntry entry) {
         Runnable action = () -> {
             mBubblesManagerOptional.ifPresent(bubblesManager ->
                     bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
@@ -258,9 +256,8 @@
      * @param row   row for that notification
      */
     @Override
-    public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
-        expectNotNull(TAG, "entry", entry);
-        expectNotNull(TAG, "row", row);
+    public void onNotificationClicked(@NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row) {
         mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
                 mKeyguardStateController.isVisible(),
                 mNotificationShadeWindowController.getPanelExpanded());
@@ -442,8 +439,7 @@
      * @param entry notification entry that is dropped.
      */
     @Override
-    public void onDragSuccess(NotificationEntry entry) {
-        expectNotNull(TAG, "entry", entry);
+    public void onDragSuccess(@NonNull NotificationEntry entry) {
         // this method is not responsible for intent sending.
         // will focus follow operation only after drag-and-drop that notification.
         final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
@@ -534,10 +530,8 @@
     }
 
     @Override
-    public void startNotificationGutsIntent(final Intent intent, final int appUid,
-            ExpandableNotificationRow row) {
-        expectNotNull(TAG, "intent", intent);
-        expectNotNull(TAG, "row", row);
+    public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
+            @NonNull ExpandableNotificationRow row) {
         boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 09e191d..92d0ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.core.StatusBarOrchestrator
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -45,7 +46,7 @@
 import kotlinx.coroutines.CoroutineScope
 
 /** Similar in purpose to [StatusBarModule], but scoped only to phones */
-@Module
+@Module(includes = [PrivacyDotViewControllerModule::class])
 interface StatusBarPhoneModule {
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index cec77c1..1bb4e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -16,16 +16,15 @@
 
 import android.content.res.Configuration;
 
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
 /**
  * Common listener for configuration or subsets of configuration changes (like density or
  * font scaling), providing easy static dependence on these events.
  */
-public interface ConfigurationController extends CallbackController<ConfigurationListener> {
-
-    /** Alert controller of a change in the configuration. */
-    void onConfigurationChanged(Configuration newConfiguration);
+public interface ConfigurationController extends CallbackController<ConfigurationListener>,
+        ConfigurationForwarder {
 
     /** Alert controller of a change in between light and dark themes. */
     void notifyThemeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index c256e64..00116aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -71,6 +72,7 @@
             new UpdateMonitorCallback();
     private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
     private final KeyguardUpdateMonitorLogger mLogger;
+    private final Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
 
     private boolean mCanDismissLockScreen;
     private boolean mShowing;
@@ -123,6 +125,7 @@
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             KeyguardUpdateMonitorLogger logger,
             DumpManager dumpManager,
+            Lazy<KeyguardInteractor> keyguardInteractor,
             FeatureFlags featureFlags,
             SelectedUserInteractor userInteractor) {
         mContext = context;
@@ -133,6 +136,7 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
         mFeatureFlags = featureFlags;
+        mKeyguardInteractorLazy = keyguardInteractor;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
@@ -354,6 +358,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
                     keyguardGoingAway ? 1 : 0);
             mKeyguardGoingAway = keyguardGoingAway;
+            mKeyguardInteractorLazy.get().setIsKeyguardGoingAway(keyguardGoingAway);
             invokeForEachCallback(Callback::onKeyguardGoingAwayChanged);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b81af86..c7bd5a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 import com.android.systemui.statusbar.policy.BatteryControllerLogger;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
@@ -186,6 +187,13 @@
             DevicePostureControllerImpl devicePostureControllerImpl);
 
     /** */
+    @Binds
+    @SysUISingleton
+    @GlobalConfig
+    ConfigurationForwarder provideGlobalConfigurationForwarder(
+            @GlobalConfig ConfigurationController configurationController);
+
+    /** */
     @Provides
     @SysUISingleton
     @GlobalConfig
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index e89a31f..618722a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -27,7 +27,10 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
 import com.android.systemui.res.R
 import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureRecognizer
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 @Composable
 fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -48,7 +51,17 @@
                 ),
         )
     val recognizer = rememberBackGestureRecognizer(LocalContext.current.resources)
-    GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
+    val gestureUiState: Flow<GestureUiState> =
+        remember(recognizer) {
+            GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
+                it.toGestureUiState(
+                    progressStartMark = "",
+                    progressEndMark = "",
+                    successAnimation = R.raw.trackpad_back_success,
+                )
+            }
+        }
+    GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
 }
 
 @Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 7899f5b..11e1ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.touchpad.tutorial.ui.composable
 
 import androidx.activity.compose.BackHandler
+import androidx.annotation.RawRes
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
@@ -31,23 +32,49 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
+import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
+import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted
 import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
 import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
+import kotlinx.coroutines.flow.Flow
 
-fun GestureState.toTutorialActionState(): TutorialActionState {
+sealed interface GestureUiState {
+    data object NotStarted : GestureUiState
+
+    data class Finished(@RawRes val successAnimation: Int) : GestureUiState
+
+    data class InProgress(
+        val progress: Float = 0f,
+        val progressStartMark: String = "",
+        val progressEndMark: String = "",
+    ) : GestureUiState
+}
+
+fun GestureState.toGestureUiState(
+    progressStartMark: String,
+    progressEndMark: String,
+    successAnimation: Int,
+): GestureUiState {
+    return when (this) {
+        GestureState.NotStarted -> NotStarted
+        is GestureState.InProgress ->
+            GestureUiState.InProgress(this.progress, progressStartMark, progressEndMark)
+        is GestureState.Finished -> GestureUiState.Finished(successAnimation)
+    }
+}
+
+fun GestureUiState.toTutorialActionState(): TutorialActionState {
     return when (this) {
         NotStarted -> TutorialActionState.NotStarted
         // progress is disabled for now as views are not ready to handle varying progress
-        is InProgress -> TutorialActionState.InProgress(0f)
-        Finished -> TutorialActionState.Finished
+        is GestureUiState.InProgress -> TutorialActionState.InProgress(progress = 0f)
+        is Finished -> TutorialActionState.Finished
     }
 }
 
@@ -55,15 +82,13 @@
 fun GestureTutorialScreen(
     screenConfig: TutorialScreenConfig,
     gestureRecognizer: GestureRecognizer,
+    gestureUiStateFlow: Flow<GestureUiState>,
     onDoneButtonClicked: () -> Unit,
     onBack: () -> Unit,
 ) {
     BackHandler(onBack = onBack)
-    var gestureState: GestureState by remember { mutableStateOf(NotStarted) }
     var easterEggTriggered by remember { mutableStateOf(false) }
-    LaunchedEffect(gestureRecognizer) {
-        gestureRecognizer.addGestureStateCallback { gestureState = it }
-    }
+    val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted)
     val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered = true }
     val gestureHandler =
         remember(gestureRecognizer) { TouchpadGestureHandler(gestureRecognizer, easterEggMonitor) }
@@ -84,7 +109,7 @@
 @Composable
 private fun TouchpadGesturesHandlingBox(
     gestureHandler: TouchpadGestureHandler,
-    gestureState: GestureState,
+    gestureState: GestureUiState,
     easterEggTriggered: Boolean,
     resetEasterEggFlag: () -> Unit,
     modifier: Modifier = Modifier,
@@ -110,7 +135,7 @@
                 .pointerInteropFilter(
                     onTouchEvent = { event ->
                         // FINISHED is the final state so we don't need to process touches anymore
-                        if (gestureState == Finished) {
+                        if (gestureState is Finished) {
                             false
                         } else {
                             gestureHandler.onMotionEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 3ddf760..05871ce 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -25,8 +25,11 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
 import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
 import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
 import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureRecognizer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 @Composable
 fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -47,7 +50,17 @@
                 ),
         )
     val recognizer = rememberHomeGestureRecognizer(LocalContext.current.resources)
-    GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
+    val gestureUiState: Flow<GestureUiState> =
+        remember(recognizer) {
+            GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
+                it.toGestureUiState(
+                    progressStartMark = "",
+                    progressEndMark = "",
+                    successAnimation = R.raw.trackpad_home_success,
+                )
+            }
+        }
+    GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
 }
 
 @Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 30a21bf..4fd1644 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -25,8 +25,11 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
 import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
 import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
 import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecognizer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 @Composable
 fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -47,7 +50,17 @@
                 ),
         )
     val recognizer = rememberRecentAppsGestureRecognizer(LocalContext.current.resources)
-    GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
+    val gestureUiState: Flow<GestureUiState> =
+        remember(recognizer) {
+            GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
+                it.toGestureUiState(
+                    progressStartMark = "",
+                    progressEndMark = "",
+                    successAnimation = R.raw.trackpad_recent_apps_success,
+                )
+            }
+        }
+    GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
 }
 
 @Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
index 80f8003..024048c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
@@ -33,6 +33,10 @@
         gestureStateChangedCallback = callback
     }
 
+    override fun clearGestureStateCallback() {
+        gestureStateChangedCallback = {}
+    }
+
     override fun accept(event: MotionEvent) {
         if (!isThreeFingerTouchpadSwipe(event)) return
         val gestureState = distanceTracker.processEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt
new file mode 100644
index 0000000..23e31b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.touchpad.tutorial.ui.gesture
+
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+class GestureFlowAdapter(gestureRecognizer: GestureRecognizer) {
+
+    val gestureStateAsFlow: Flow<GestureState> = conflatedCallbackFlow {
+        val callback: (GestureState) -> Unit = { trySend(it) }
+        gestureRecognizer.addGestureStateCallback(callback)
+        awaitClose { gestureRecognizer.clearGestureStateCallback() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
index d146268..68a2ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
@@ -22,6 +22,8 @@
 /** Based on passed [MotionEvent]s recognizes different states of gesture and notifies callback. */
 interface GestureRecognizer : Consumer<MotionEvent> {
     fun addGestureStateCallback(callback: (GestureState) -> Unit)
+
+    fun clearGestureStateCallback()
 }
 
 fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index 2b84a4c..b804b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -29,6 +29,10 @@
         gestureStateChangedCallback = callback
     }
 
+    override fun clearGestureStateCallback() {
+        gestureStateChangedCallback = {}
+    }
+
     override fun accept(event: MotionEvent) {
         if (!isThreeFingerTouchpadSwipe(event)) return
         val gestureState = distanceTracker.processEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index 69b7c5e..7d484ee 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -38,6 +38,10 @@
         gestureStateChangedCallback = callback
     }
 
+    override fun clearGestureStateCallback() {
+        gestureStateChangedCallback = {}
+    }
+
     override fun accept(event: MotionEvent) {
         if (!isThreeFingerTouchpadSwipe(event)) return
         val gestureState = distanceTracker.processEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 7c5116d..07509e6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -64,6 +64,8 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.RotateDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.os.Debug;
@@ -115,6 +117,7 @@
 import com.android.internal.view.RotationPolicy;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.haptics.slider.HapticSliderViewBinder;
@@ -140,6 +143,7 @@
 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
+import com.google.android.msdl.domain.MSDLPlayer;
 import com.google.common.collect.ImmutableList;
 
 import dagger.Lazy;
@@ -315,6 +319,7 @@
     private final Lazy<SecureSettings> mSecureSettings;
     private int mDialogTimeoutMillis;
     private final VibratorHelper mVibratorHelper;
+    private final MSDLPlayer mMSDLPlayer;
     private final com.android.systemui.util.time.SystemClock mSystemClock;
     private final VolumePanelFlag mVolumePanelFlag;
     private final VolumeDialogInteractor mInteractor;
@@ -340,12 +345,14 @@
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
+            MSDLPlayer msdlPlayer,
             com.android.systemui.util.time.SystemClock systemClock,
             VolumeDialogInteractor interactor) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
         mVibratorHelper = vibratorHelper;
+        mMSDLPlayer = msdlPlayer;
         mSystemClock = systemClock;
         mShouldListenForJank = shouldListenForJank;
         mController = volumeDialogController;
@@ -652,6 +659,11 @@
             mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
         }
 
+        if (Flags.hideRingerButtonInSingleVolumeMode() && AudioSystem.isSingleVolume(mContext)) {
+            mRingerAndDrawerContainer.setVisibility(INVISIBLE);
+            mRinger.setVisibility(INVISIBLE);
+        }
+
         mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon);
         mSelectedRingerContainer = mDialog.findViewById(
                 R.id.volume_new_ringer_active_icon_container);
@@ -927,7 +939,7 @@
     }
 
     private void addSliderHapticsToRow(VolumeRow row) {
-        row.createPlugin(mVibratorHelper, mSystemClock);
+        row.createPlugin(mVibratorHelper, mMSDLPlayer, mSystemClock);
         HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
     }
 
@@ -2337,10 +2349,31 @@
             return;
         }
 
-        final ColorDrawable solidDrawable = new ColorDrawable(
+        LayerDrawable background;
+        // mRingerAndDrawerContainer has rounded corner.
+        // But when it's not visible, mTopContainer needs to have rounded corner.
+        if (Flags.hideRingerButtonInSingleVolumeMode()
+                && mRingerAndDrawerContainer.getVisibility() != VISIBLE
+        ) {
+            float[] radius = new float[] {
+                mDialogCornerRadius, mDialogCornerRadius,  // Top-left corner
+                mDialogCornerRadius, mDialogCornerRadius,  // Top-right corner
+                0, 0,  // Bottom-right corner
+                0, 0   // Bottom-left corner
+            };
+
+            ShapeDrawable roundedDrawable = new ShapeDrawable(
+                    new RoundRectShape(radius, null, null));
+            roundedDrawable.getPaint().setColor(Utils.getColorAttrDefaultColor(
+                    mContext, com.android.internal.R.attr.colorSurface));
+
+            background = new LayerDrawable(new Drawable[] { roundedDrawable });
+        } else {
+            final ColorDrawable solidDrawable = new ColorDrawable(
                 Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface));
 
-        final LayerDrawable background = new LayerDrawable(new Drawable[] { solidDrawable });
+            background = new LayerDrawable(new Drawable[] { solidDrawable });
+        }
 
         // Size the solid color to match the primary volume row. In landscape, extend it upwards
         // slightly so that it fills in the bottom corners of the ringer icon, whose background is
@@ -2707,11 +2740,13 @@
 
         void createPlugin(
                 VibratorHelper vibratorHelper,
+                MSDLPlayer msdlPlayer,
                 com.android.systemui.util.time.SystemClock systemClock) {
             if (mHapticPlugin != null) return;
 
             mHapticPlugin = new SeekbarHapticPlugin(
                 vibratorHelper,
+                msdlPlayer,
                 systemClock,
                 sSliderHapticFeedbackConfig,
                 sSliderTrackerConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index ed8de69..2009143 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -50,6 +50,8 @@
 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
@@ -121,6 +123,7 @@
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
+            MSDLPlayer msdlPlayer,
             SystemClock systemClock,
             VolumeDialogInteractor interactor) {
         if (Flags.volumeRedesign()) {
@@ -144,6 +147,7 @@
                     dumpManager,
                     secureSettings,
                     vibratorHelper,
+                    msdlPlayer,
                     systemClock,
                     interactor);
             impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
new file mode 100644
index 0000000..7265b821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.volume.dialog.ringer.domain
+
+import android.media.AudioManager
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.provider.Settings
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Exposes [VolumeDialogRingerModel]. */
+@VolumeDialog
+class VolumeDialogRingerInteractor
+@Inject
+constructor(
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    volumeDialogStateInteractor: VolumeDialogStateInteractor,
+    private val controller: VolumeDialogController,
+) {
+
+    val ringerModel: Flow<VolumeDialogRingerModel> =
+        volumeDialogStateInteractor.volumeDialogState
+            .mapNotNull { toRingerModel(it) }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
+
+    private fun toRingerModel(state: VolumeDialogStateModel): VolumeDialogRingerModel? {
+        return state.streamModels[AudioManager.STREAM_RING]?.let {
+            VolumeDialogRingerModel(
+                availableModes =
+                    mutableListOf(RingerMode(RINGER_MODE_NORMAL), RingerMode(RINGER_MODE_SILENT))
+                        .also { list ->
+                            if (controller.hasVibrator()) {
+                                list.add(RingerMode(RINGER_MODE_VIBRATE))
+                            }
+                        },
+                currentRingerMode = RingerMode(state.ringerModeInternal),
+                isEnabled =
+                    !(state.zenMode == Settings.Global.ZEN_MODE_ALARMS ||
+                        state.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS ||
+                        (state.zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS &&
+                            state.disallowRinger)),
+                isMuted = it.level == 0 || it.muted,
+                level = it.level,
+                levelMax = it.levelMax,
+            )
+        }
+    }
+
+    fun setRingerMode(ringerMode: RingerMode) {
+        controller.setRingerMode(ringerMode.value, false)
+    }
+
+    fun scheduleTouchFeedback() {
+        controller.scheduleTouchFeedback()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
new file mode 100644
index 0000000..cf23f1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.volume.dialog.ringer.shared.model
+
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models the state of the volume dialog ringer. */
+data class VolumeDialogRingerModel(
+    val availableModes: List<RingerMode>,
+    /** Current ringer mode internal */
+    val currentRingerMode: RingerMode,
+    /** whether the ringer is allowed given the current ZenMode */
+    val isEnabled: Boolean,
+    /** Whether the current ring stream level is zero or the controller state is muted */
+    val isMuted: Boolean,
+    /** Ring stream level */
+    val level: Int,
+    /** Ring stream maximum level */
+    val levelMax: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02d0b57..8039e00 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -281,13 +281,13 @@
     void initSplitScreen(SplitScreen splitScreen) {
         mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
             @Override
-            public void onFinishedWakingUp() {
-                splitScreen.onFinishedWakingUp();
+            public void onStartedGoingToSleep() {
+                splitScreen.onStartedGoingToSleep();
             }
 
             @Override
-            public void onStartedGoingToSleep() {
-                splitScreen.onStartedGoingToSleep();
+            public void onStartedWakingUp() {
+                splitScreen.onStartedWakingUp();
             }
         });
         mCommandQueue.addCallback(new CommandQueue.Callbacks() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 3d1a0d0..96f4a60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -481,6 +481,25 @@
         verify(mAuthController).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat());
     }
 
+    @Test
+    @EnableFlags(android.hardware.biometrics.Flags.FLAG_SCREEN_OFF_UNLOCK_UDFPS)
+    public void udfpsLongPress_triggeredWhenDoze() {
+        // GIVEN device is DOZE
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+        // WHEN udfps long-press is triggered
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, 100, 100,
+                new float[]{0, 1, 2, 3, 4});
+
+        // THEN the pulse is NOT dropped
+        verify(mDozeLog, never()).tracePulseDropped(anyString(), any());
+
+        // WHEN the screen state is OFF
+        mTriggers.onScreenState(Display.STATE_OFF);
+
+        // THEN aod interrupt never be sent
+        verify(mAuthController, never()).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat());
+    }
 
     @Test
     public void udfpsLongPress_dozeState_notRegistered() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 6b60740..7383faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -21,6 +21,9 @@
 import android.view.KeyEvent
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -41,13 +44,17 @@
 import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
 import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -76,6 +83,7 @@
             it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
             it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
             it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
+            it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { context })
         }
 
     private val testScope = kosmos.testScope
@@ -253,7 +261,7 @@
             whenever(
                     mockRoleManager.getRoleHoldersAsUser(
                         RoleManager.ROLE_HOME,
-                        fakeUserTracker.userHandle
+                        fakeUserTracker.userHandle,
                     )
                 )
                 .thenReturn(listOf(TestShortcuts.currentAppPackageName))
@@ -283,15 +291,25 @@
             val activeUiState = uiState as ShortcutsUiState.Active
             assertThat(activeUiState.shortcutCategories)
                 .containsExactly(
-                    ShortcutCategory(
-                        System,
-                        subCategoryWithShortcutLabels("first Foo shortcut1"),
-                        subCategoryWithShortcutLabels("second foO shortcut2")
+                    ShortcutCategoryUi(
+                        label = "System",
+                        iconSource = IconSource(imageVector = Icons.Default.Tv),
+                        shortcutCategory =
+                            ShortcutCategory(
+                                System,
+                                subCategoryWithShortcutLabels("first Foo shortcut1"),
+                                subCategoryWithShortcutLabels("second foO shortcut2"),
+                            ),
                     ),
-                    ShortcutCategory(
-                        MultiTasking,
-                        subCategoryWithShortcutLabels("third FoO shortcut1")
-                    )
+                    ShortcutCategoryUi(
+                        label = "Multitasking",
+                        iconSource = IconSource(imageVector = Icons.Default.VerticalSplit),
+                        shortcutCategory =
+                            ShortcutCategory(
+                                MultiTasking,
+                                subCategoryWithShortcutLabels("third FoO shortcut1"),
+                            ),
+                    ),
                 )
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index e981d62..cad22d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -16,11 +16,11 @@
 
 package com.android.systemui.keyguard;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index dcf32a5..51c8525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -38,11 +38,11 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -61,7 +61,7 @@
 private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
 
 private fun <T> anyObject(): T {
-    return Mockito.anyObject<T>()
+    return ArgumentMatchers.any<T>()
 }
 
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index bdd8dc8..2aa300d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -21,7 +21,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 2f41ac17..338ed75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -19,9 +19,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.anyObject;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -194,7 +193,7 @@
 
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(1))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
     }
 
     @Test
@@ -207,7 +206,7 @@
 
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(1))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
     }
 
     @Test
@@ -220,7 +219,7 @@
 
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(0))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
     }
 
     @Test
@@ -233,7 +232,7 @@
 
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(0))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
     }
 
     @Test
@@ -243,14 +242,14 @@
 
         // success registering skin thermal event listener
         when(mThermalServiceMock.registerThermalEventListenerWithType(
-                anyObject(), eq(Temperature.TYPE_SKIN))).thenReturn(true);
+                any(), eq(Temperature.TYPE_SKIN))).thenReturn(true);
 
         mPowerUI.doSkinThermalEventListenerRegistration();
 
         // verify registering skin thermal event listener, return true (success)
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(1))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
 
         // Settings SHOW_TEMPERATURE_WARNING is set to 0
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
@@ -259,7 +258,7 @@
 
         // verify unregistering skin thermal event listener
         TestableLooper.get(this).processAllMessages();
-        verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(anyObject());
+        verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(any());
     }
 
     @Test
@@ -269,14 +268,14 @@
 
         // fail registering skin thermal event listener
         when(mThermalServiceMock.registerThermalEventListenerWithType(
-                anyObject(), eq(Temperature.TYPE_SKIN))).thenReturn(false);
+                any(), eq(Temperature.TYPE_SKIN))).thenReturn(false);
 
         mPowerUI.doSkinThermalEventListenerRegistration();
 
         // verify registering skin thermal event listener, return false (fail)
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(1))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
 
         // Settings SHOW_TEMPERATURE_WARNING is set to 0
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
@@ -285,7 +284,7 @@
 
         // verify that cannot unregister listener (current state is unregistered)
         TestableLooper.get(this).processAllMessages();
-        verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(anyObject());
+        verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(any());
 
         // Settings SHOW_TEMPERATURE_WARNING is set to 1
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 1);
@@ -295,7 +294,7 @@
         // verify that can register listener (current state is unregistered)
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(2))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
     }
 
     @Test
@@ -305,14 +304,14 @@
 
         // success registering usb thermal event listener
         when(mThermalServiceMock.registerThermalEventListenerWithType(
-                anyObject(), eq(Temperature.TYPE_USB_PORT))).thenReturn(true);
+                any(), eq(Temperature.TYPE_USB_PORT))).thenReturn(true);
 
         mPowerUI.doUsbThermalEventListenerRegistration();
 
         // verify registering usb thermal event listener, return true (success)
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(1))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
 
         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 0
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
@@ -320,7 +319,7 @@
         // verify unregistering usb thermal event listener
         mPowerUI.doUsbThermalEventListenerRegistration();
         TestableLooper.get(this).processAllMessages();
-        verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(anyObject());
+        verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(any());
     }
 
     @Test
@@ -330,14 +329,14 @@
 
         // fail registering usb thermal event listener
         when(mThermalServiceMock.registerThermalEventListenerWithType(
-                anyObject(), eq(Temperature.TYPE_USB_PORT))).thenReturn(false);
+                any(), eq(Temperature.TYPE_USB_PORT))).thenReturn(false);
 
         mPowerUI.doUsbThermalEventListenerRegistration();
 
         // verify registering usb thermal event listener, return false (fail)
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(1))
-                .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+                .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
 
         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 0
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
@@ -346,7 +345,7 @@
 
         // verify that cannot unregister listener (current state is unregistered)
         TestableLooper.get(this).processAllMessages();
-        verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(anyObject());
+        verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(any());
 
         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 1
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 1);
@@ -356,7 +355,7 @@
         // verify that can register listener (current state is unregistered)
         TestableLooper.get(this).processAllMessages();
         verify(mThermalServiceMock, times(2)).registerThermalEventListenerWithType(
-                anyObject(), eq(Temperature.TYPE_USB_PORT));
+                any(), eq(Temperature.TYPE_USB_PORT));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index dad65f5..f6d5732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -25,7 +25,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 748c7d9..296478b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -36,7 +36,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
deleted file mode 100644
index 3756ec1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
+++ /dev/null
@@ -1,200 +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.screenshot
-
-import android.app.ActivityTaskManager.RootTaskInfo
-import android.app.IActivityTaskManager
-import android.content.ComponentName
-import android.content.Context
-import android.graphics.Rect
-import android.os.UserHandle
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-import com.android.systemui.screenshot.policy.ActivityType.Home
-import com.android.systemui.screenshot.policy.ActivityType.Undefined
-import com.android.systemui.screenshot.policy.WindowingMode.FullScreen
-import com.android.systemui.screenshot.policy.WindowingMode.PictureInPicture
-import com.android.systemui.screenshot.policy.newChildTask
-import com.android.systemui.screenshot.policy.newRootTaskInfo
-import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Test
-import org.junit.runner.RunWith
-
-// The following values are chosen to be distinct from commonly seen real values
-private const val DISPLAY_ID = 100
-private const val PRIMARY_USER = 2000
-private const val MANAGED_PROFILE_USER = 3000
-
-@RunWith(AndroidTestingRunner::class)
-class ScreenshotPolicyImplTest : SysuiTestCase() {
-
-    @Test
-    fun testToDisplayContentInfo() {
-        assertThat(fullScreenWorkProfileTask.toDisplayContentInfo())
-            .isEqualTo(
-                DisplayContentInfo(
-                    ComponentName(
-                        "com.google.android.apps.nbu.files",
-                        "com.google.android.apps.nbu.files.home.HomeActivity"
-                    ),
-                    Rect(0, 0, 1080, 2400),
-                    UserHandle.of(MANAGED_PROFILE_USER),
-                    65
-                )
-            )
-    }
-
-    @Test
-    fun findPrimaryContent_ignoresPipTask() = runBlocking {
-        val policy =
-            fakeTasksPolicyImpl(
-                mContext,
-                shadeExpanded = false,
-                tasks = listOf(pipTask, fullScreenWorkProfileTask, launcherTask, emptyTask)
-            )
-
-        val info = policy.findPrimaryContent(DISPLAY_ID)
-        assertThat(info).isEqualTo(fullScreenWorkProfileTask.toDisplayContentInfo())
-    }
-
-    @Test
-    fun findPrimaryContent_shadeExpanded_ignoresTopTask() = runBlocking {
-        val policy =
-            fakeTasksPolicyImpl(
-                mContext,
-                shadeExpanded = true,
-                tasks = listOf(fullScreenWorkProfileTask, launcherTask, emptyTask)
-            )
-
-        val info = policy.findPrimaryContent(DISPLAY_ID)
-        assertThat(info).isEqualTo(policy.systemUiContent)
-    }
-
-    @Test
-    fun findPrimaryContent_emptyTaskList() = runBlocking {
-        val policy = fakeTasksPolicyImpl(mContext, shadeExpanded = false, tasks = listOf())
-
-        val info = policy.findPrimaryContent(DISPLAY_ID)
-        assertThat(info).isEqualTo(policy.systemUiContent)
-    }
-
-    @Test
-    fun findPrimaryContent_workProfileNotOnTop() = runBlocking {
-        val policy =
-            fakeTasksPolicyImpl(
-                mContext,
-                shadeExpanded = false,
-                tasks = listOf(launcherTask, fullScreenWorkProfileTask, emptyTask)
-            )
-
-        val info = policy.findPrimaryContent(DISPLAY_ID)
-        assertThat(info).isEqualTo(launcherTask.toDisplayContentInfo())
-    }
-
-    private fun fakeTasksPolicyImpl(
-        context: Context,
-        shadeExpanded: Boolean,
-        tasks: List<RootTaskInfo>
-    ): ScreenshotPolicyImpl {
-        val userManager = mock<UserManager>()
-        val atmService = mock<IActivityTaskManager>()
-        val dispatcher = Dispatchers.Unconfined
-        val displayTracker = FakeDisplayTracker(context)
-
-        return object :
-            ScreenshotPolicyImpl(context, userManager, atmService, dispatcher, displayTracker) {
-            override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
-            override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
-            override suspend fun isNotificationShadeExpanded() = shadeExpanded
-        }
-    }
-
-    private val pipTask =
-        newRootTaskInfo(
-            taskId = 66,
-            userId = PRIMARY_USER,
-            displayId = DISPLAY_ID,
-            bounds = Rect(628, 1885, 1038, 2295),
-            windowingMode = PictureInPicture,
-            topActivity = ComponentName.unflattenFromString(YOUTUBE_PIP_ACTIVITY),
-        ) {
-            listOf(newChildTask(taskId = 66, userId = 0, name = YOUTUBE_HOME_ACTIVITY))
-        }
-
-    private val fullScreenWorkProfileTask =
-        newRootTaskInfo(
-            taskId = 65,
-            userId = MANAGED_PROFILE_USER,
-            displayId = DISPLAY_ID,
-            bounds = Rect(0, 0, 1080, 2400),
-            windowingMode = FullScreen,
-            topActivity = ComponentName.unflattenFromString(FILES_HOME_ACTIVITY),
-        ) {
-            listOf(
-                newChildTask(taskId = 65, userId = MANAGED_PROFILE_USER, name = FILES_HOME_ACTIVITY)
-            )
-        }
-    private val launcherTask =
-        newRootTaskInfo(
-            taskId = 1,
-            userId = PRIMARY_USER,
-            displayId = DISPLAY_ID,
-            activityType = Home,
-            windowingMode = FullScreen,
-            bounds = Rect(0, 0, 1080, 2400),
-            topActivity = ComponentName.unflattenFromString(LAUNCHER_ACTIVITY),
-        ) {
-            listOf(newChildTask(taskId = 1, userId = 0, name = LAUNCHER_ACTIVITY))
-        }
-
-    private val emptyTask =
-        newRootTaskInfo(
-            taskId = 2,
-            userId = PRIMARY_USER,
-            displayId = DISPLAY_ID,
-            visible = false,
-            running = false,
-            numActivities = 0,
-            activityType = Undefined,
-            bounds = Rect(0, 0, 1080, 2400),
-        ) {
-            listOf(
-                newChildTask(taskId = 3, name = ""),
-                newChildTask(taskId = 4, name = ""),
-            )
-        }
-}
-
-private const val YOUTUBE_HOME_ACTIVITY =
-    "com.google.android.youtube/" + "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"
-
-private const val FILES_HOME_ACTIVITY =
-    "com.google.android.apps.nbu.files/" + "com.google.android.apps.nbu.files.home.HomeActivity"
-
-private const val YOUTUBE_PIP_ACTIVITY =
-    "com.google.android.youtube/" +
-        "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
-
-private const val LAUNCHER_ACTIVITY =
-    "com.google.android.apps.nexuslauncher/" +
-        "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
index 040a9e9..8bd8b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package systemui.shared.clocks.view
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index e9222c3e..3ad0605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -19,8 +19,8 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2b5e014..b730b37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -21,8 +21,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index a75d7b2..da0029f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -21,6 +21,8 @@
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
 
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
@@ -777,6 +779,24 @@
     }
 
     @Test
+    public void indicationAreaHidden_untilBatteryInfoArrives() {
+        createController();
+        // level of -1 indicates missing info
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_UNKNOWN,
+                -1 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
+                0 /* maxChargingWattage */, true /* present */);
+
+        mController.setVisible(true);
+        mStatusBarStateListener.onDozingChanged(true);
+        reset(mIndicationArea);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        // VISIBLE is always called first
+        verify(mIndicationArea).setVisibility(VISIBLE);
+        verify(mIndicationArea).setVisibility(GONE);
+    }
+
+    @Test
     public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
index b18b7f8..72ffa0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -16,41 +16,39 @@
 
 package com.android.systemui.statusbar.commandline
 
-import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-
-import org.mockito.ArgumentMatchers.anyList
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
 import java.io.PrintWriter
 import java.io.StringWriter
 import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
 
 private fun <T> anyObject(): T {
-    return Mockito.anyObject<T>()
+    return any<T>()
 }
 
 private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @SmallTest
 class CommandRegistryTest : SysuiTestCase() {
     lateinit var registry: CommandRegistry
-    val inLineExecutor = object : Executor {
-        override fun execute(command: Runnable) {
-            command.run()
+    val inLineExecutor =
+        object : Executor {
+            override fun execute(command: Runnable) {
+                command.run()
+            }
         }
-    }
 
     val writer: PrintWriter = PrintWriter(StringWriter())
 
@@ -83,11 +81,9 @@
     }
 
     class FakeCommand() : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-        }
+        override fun execute(pw: PrintWriter, args: List<String>) {}
 
-        override fun help(pw: PrintWriter) {
-        }
+        override fun help(pw: PrintWriter) {}
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 7bd77a6..5fce08b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -18,7 +18,7 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 
 import android.os.HandlerThread;
 import android.telephony.SubscriptionInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 83dbfa0..b00f9e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -25,10 +25,10 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -297,7 +297,8 @@
         assertNotNull(mDefaultCallbackInWifiTracker);
         assertNotNull(mDefaultCallbackInNetworkController);
         verify(mMockCm, atLeastOnce()).registerNetworkCallback(
-                isA(NetworkRequest.class), callbackArg.capture(), isA(Handler.class));
+                isA(NetworkRequest.class), callbackArg.capture(),
+                isA(Handler.class));
         mNetworkCallback = callbackArg.getValue();
         assertNotNull(mNetworkCallback);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 0b5f8d5..723c0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -74,22 +74,31 @@
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.wmshell.BubblesManager
 import java.util.Optional
-import junit.framework.Assert
 import kotlin.test.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
+import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
 import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 /** Tests for [NotificationGutsManager] with the scene container enabled. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
@@ -99,7 +108,7 @@
         NotificationChannel(
             TEST_CHANNEL_ID,
             TEST_CHANNEL_ID,
-            NotificationManager.IMPORTANCE_DEFAULT
+            NotificationManager.IMPORTANCE_DEFAULT,
         )
 
     private val kosmos = testKosmos()
@@ -146,7 +155,7 @@
         MockitoAnnotations.initMocks(this)
         allowTestableLooperAsMainThread()
         helper = NotificationTestHelper(mContext, mDependency)
-        Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+        whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
         windowRootViewVisibilityInteractor =
             WindowRootViewVisibilityInteractor(
                 testScope.backgroundScope,
@@ -185,12 +194,12 @@
                 deviceProvisionedController,
                 metricsLogger,
                 headsUpManager,
-                activityStarter
+                activityStarter,
             )
         gutsManager.setUpWithPresenter(
             presenter,
             notificationListContainer,
-            onSettingsClickListener
+            onSettingsClickListener,
         )
         gutsManager.setNotificationActivityStarter(notificationActivityStarter)
         gutsManager.start()
@@ -198,49 +207,31 @@
 
     @Test
     fun testOpenAndCloseGuts() {
-        val guts = Mockito.spy(NotificationGuts(mContext))
-        Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
-            ->
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
             handler.post((invocation.arguments[0] as Runnable))
             null
         }
 
         // Test doesn't support animation since the guts view is not attached.
-        Mockito.doNothing()
-            .`when`(guts)
-            .openControls(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.any(Runnable::class.java)
-            )
+        doNothing()
+            .whenever(guts)
+            .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
         val realRow = createTestNotificationRow()
         val menuItem = createTestMenuItem(realRow)
-        val row = Mockito.spy(realRow)
-        Mockito.`when`(row!!.windowToken).thenReturn(Binder())
-        Mockito.`when`(row.guts).thenReturn(guts)
+        val row = spy(realRow)
+        whenever(row!!.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
         Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
         assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
         executor.runAllReady()
-        verify(guts)
-            .openControls(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.any(Runnable::class.java)
-            )
+        verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
         verify(headsUpManager).setGutsShown(realRow!!.entry, true)
         assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
         gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
         verify(guts)
-            .closeControls(
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean()
-            )
-        verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+            .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
+        verify(row, times(1)).setGutsView(any())
         executor.runAllReady()
         verify(headsUpManager).setGutsShown(realRow.entry, false)
     }
@@ -250,7 +241,7 @@
         // First, start out lockscreen or shade as not visible
         setIsLockscreenOrShadeVisible(false)
         testScope.testScheduler.runCurrent()
-        val guts = Mockito.mock(NotificationGuts::class.java)
+        val guts = mock<NotificationGuts>()
         gutsManager.exposedGuts = guts
 
         // WHEN the lockscreen or shade becomes visible
@@ -258,15 +249,9 @@
         testScope.testScheduler.runCurrent()
 
         // THEN the guts are not closed
-        verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
-        verify(guts, Mockito.never())
-            .closeControls(
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean()
-            )
+        verify(guts, never()).removeCallbacks(any())
+        verify(guts, never())
+            .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
     }
 
     @Test
@@ -274,7 +259,7 @@
         // First, start out lockscreen or shade as visible
         setIsLockscreenOrShadeVisible(true)
         testScope.testScheduler.runCurrent()
-        val guts = Mockito.mock(NotificationGuts::class.java)
+        val guts = mock<NotificationGuts>()
         gutsManager.exposedGuts = guts
 
         // WHEN the lockscreen or shade is no longer visible
@@ -282,14 +267,14 @@
         testScope.testScheduler.runCurrent()
 
         // THEN the guts are closed
-        verify(guts).removeCallbacks(ArgumentMatchers.any())
+        verify(guts).removeCallbacks(anyOrNull())
         verify(guts)
             .closeControls(
-                /* leavebehinds= */ ArgumentMatchers.eq(true),
-                /* controls= */ ArgumentMatchers.eq(true),
-                /* x= */ ArgumentMatchers.anyInt(),
-                /* y= */ ArgumentMatchers.anyInt(),
-                /* force= */ ArgumentMatchers.eq(true)
+                /* leavebehinds= */ eq(true),
+                /* controls= */ eq(true),
+                /* x= */ any<Int>(),
+                /* y= */ any<Int>(),
+                /* force= */ eq(true),
             )
     }
 
@@ -304,95 +289,68 @@
         testScope.testScheduler.runCurrent()
 
         // THEN the list container is reset
-        verify(notificationListContainer)
-            .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+        verify(notificationListContainer).resetExposedMenuView(any<Boolean>(), any<Boolean>())
     }
 
     @Test
     fun testChangeDensityOrFontScale() {
-        val guts = Mockito.spy(NotificationGuts(mContext))
-        Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
-            ->
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
             handler.post((invocation.arguments[0] as Runnable))
             null
         }
 
         // Test doesn't support animation since the guts view is not attached.
-        Mockito.doNothing()
-            .`when`(guts)
-            .openControls(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.any(Runnable::class.java)
-            )
+        doNothing()
+            .whenever(guts)
+            .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
         val realRow = createTestNotificationRow()
         val menuItem = createTestMenuItem(realRow)
-        val row = Mockito.spy(realRow)
-        Mockito.`when`(row!!.windowToken).thenReturn(Binder())
-        Mockito.`when`(row.guts).thenReturn(guts)
-        Mockito.doNothing().`when`(row).ensureGutsInflated()
+        val row = spy(realRow)
+        whenever(row!!.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
+        doNothing().whenever(row).ensureGutsInflated()
         val realEntry = realRow!!.entry
-        val entry = Mockito.spy(realEntry)
-        Mockito.`when`(entry.row).thenReturn(row)
-        Mockito.`when`(entry.getGuts()).thenReturn(guts)
+        val entry = spy(realEntry)
+        whenever(entry.row).thenReturn(row)
+        whenever(entry.getGuts()).thenReturn(guts)
         Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
         executor.runAllReady()
-        verify(guts)
-            .openControls(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.any(Runnable::class.java)
-            )
+        verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
 
         // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
-        verify(row).setGutsView(ArgumentMatchers.any())
+        verify(row).setGutsView(any())
         row.onDensityOrFontScaleChanged()
         gutsManager.onDensityOrFontScaleChanged(entry)
         executor.runAllReady()
         gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
         verify(guts)
-            .closeControls(
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.anyBoolean(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.anyBoolean()
-            )
+            .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
 
         // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
-        verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+        verify(row, times(2)).setGutsView(any())
     }
 
     @Test
     fun testAppOpsSettingsIntent_camera() {
         val ops = ArraySet<Int>()
         ops.add(AppOpsManager.OP_CAMERA)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
     }
 
     @Test
     fun testAppOpsSettingsIntent_mic() {
         val ops = ArraySet<Int>()
         ops.add(AppOpsManager.OP_RECORD_AUDIO)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
     }
 
     @Test
@@ -400,30 +358,22 @@
         val ops = ArraySet<Int>()
         ops.add(AppOpsManager.OP_CAMERA)
         ops.add(AppOpsManager.OP_RECORD_AUDIO)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
     }
 
     @Test
     fun testAppOpsSettingsIntent_overlay() {
         val ops = ArraySet<Int>()
         ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.lastValue.action)
     }
 
     @Test
@@ -432,15 +382,11 @@
         ops.add(AppOpsManager.OP_CAMERA)
         ops.add(AppOpsManager.OP_RECORD_AUDIO)
         ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
     }
 
     @Test
@@ -448,15 +394,11 @@
         val ops = ArraySet<Int>()
         ops.add(AppOpsManager.OP_CAMERA)
         ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
     }
 
     @Test
@@ -464,112 +406,108 @@
         val ops = ArraySet<Int>()
         ops.add(AppOpsManager.OP_RECORD_AUDIO)
         ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(notificationActivityStarter, Mockito.times(1))
-            .startNotificationGutsIntent(
-                captor.capture(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any()
-            )
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
     }
 
     @Test
     @Throws(Exception::class)
     fun testInitializeNotificationInfoView_highPriority() {
-        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
-        val row = Mockito.spy(helper.createRow())
+        val notificationInfoView = mock<NotificationInfo>()
+        val row = spy(helper.createRow())
         val entry = row.entry
         NotificationEntryHelper.modifyRanking(entry)
             .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
             .setImportance(NotificationManager.IMPORTANCE_HIGH)
             .build()
-        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
-        Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+        whenever(row.getIsNonblockable()).thenReturn(false)
+        whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
         val statusBarNotification = entry.sbn
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
         verify(notificationInfoView)
             .bindNotification(
-                ArgumentMatchers.any(PackageManager::class.java),
-                ArgumentMatchers.any(INotificationManager::class.java),
-                ArgumentMatchers.eq(onUserInteractionCallback),
-                ArgumentMatchers.eq(channelEditorDialogController),
-                ArgumentMatchers.eq(statusBarNotification.packageName),
-                ArgumentMatchers.any(NotificationChannel::class.java),
-                ArgumentMatchers.eq(entry),
-                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
-                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
-                ArgumentMatchers.any(UiEventLogger::class.java),
-                ArgumentMatchers.eq(true),
-                ArgumentMatchers.eq(false),
-                ArgumentMatchers.eq(true), /* wasShownHighPriority */
-                ArgumentMatchers.eq(assistantFeedbackController),
-                ArgumentMatchers.any(MetricsLogger::class.java)
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                eq(true),
+                eq(false),
+                eq(true), /* wasShownHighPriority */
+                eq(assistantFeedbackController),
+                any<MetricsLogger>(),
             )
     }
 
     @Test
     @Throws(Exception::class)
     fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
-        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
-        val row = Mockito.spy(helper.createRow())
+        val notificationInfoView = mock<NotificationInfo>()
+        val row = spy(helper.createRow())
         NotificationEntryHelper.modifyRanking(row.entry)
             .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
             .build()
-        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        whenever(row.getIsNonblockable()).thenReturn(false)
         val statusBarNotification = row.entry.sbn
         val entry = row.entry
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
         verify(notificationInfoView)
             .bindNotification(
-                ArgumentMatchers.any(PackageManager::class.java),
-                ArgumentMatchers.any(INotificationManager::class.java),
-                ArgumentMatchers.eq(onUserInteractionCallback),
-                ArgumentMatchers.eq(channelEditorDialogController),
-                ArgumentMatchers.eq(statusBarNotification.packageName),
-                ArgumentMatchers.any(NotificationChannel::class.java),
-                ArgumentMatchers.eq(entry),
-                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
-                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
-                ArgumentMatchers.any(UiEventLogger::class.java),
-                ArgumentMatchers.eq(true),
-                ArgumentMatchers.eq(false),
-                ArgumentMatchers.eq(false), /* wasShownHighPriority */
-                ArgumentMatchers.eq(assistantFeedbackController),
-                ArgumentMatchers.any(MetricsLogger::class.java)
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                eq(true),
+                eq(false),
+                eq(false), /* wasShownHighPriority */
+                eq(assistantFeedbackController),
+                any<MetricsLogger>(),
             )
     }
 
     @Test
     @Throws(Exception::class)
     fun testInitializeNotificationInfoView_withInitialAction() {
-        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
-        val row = Mockito.spy(helper.createRow())
+        val notificationInfoView = mock<NotificationInfo>()
+        val row = spy(helper.createRow())
         NotificationEntryHelper.modifyRanking(row.entry)
             .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
             .build()
-        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        whenever(row.getIsNonblockable()).thenReturn(false)
         val statusBarNotification = row.entry.sbn
         val entry = row.entry
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
         verify(notificationInfoView)
             .bindNotification(
-                ArgumentMatchers.any(PackageManager::class.java),
-                ArgumentMatchers.any(INotificationManager::class.java),
-                ArgumentMatchers.eq(onUserInteractionCallback),
-                ArgumentMatchers.eq(channelEditorDialogController),
-                ArgumentMatchers.eq(statusBarNotification.packageName),
-                ArgumentMatchers.any(NotificationChannel::class.java),
-                ArgumentMatchers.eq(entry),
-                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
-                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
-                ArgumentMatchers.any(UiEventLogger::class.java),
-                ArgumentMatchers.eq(true),
-                ArgumentMatchers.eq(false),
-                ArgumentMatchers.eq(false), /* wasShownHighPriority */
-                ArgumentMatchers.eq(assistantFeedbackController),
-                ArgumentMatchers.any(MetricsLogger::class.java)
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                eq(true),
+                eq(false),
+                eq(false), /* wasShownHighPriority */
+                eq(assistantFeedbackController),
+                any<MetricsLogger>(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
index dcd57f1..c2460f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
@@ -17,30 +17,27 @@
 package com.android.systemui.statusbar.policy
 
 import android.app.NotificationManager
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
-
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
-
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 private fun <T> anyObject(): T {
-    return Mockito.anyObject<T>()
+    return any<T>()
 }
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper()
 @SmallTest
 class BatteryStateNotifierTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 9bb7607..f91f373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -22,10 +22,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -120,9 +119,9 @@
 
         verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 brCaptor.capture(),
-                anyObject(),
-                anyObject(),
-                anyObject());
+                any(),
+                any(),
+                any());
 
         mBroadcastReceiver = brCaptor.getValue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index ecc7909..3007eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -92,6 +92,7 @@
 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
+import com.google.android.msdl.domain.MSDLPlayer;
 import com.google.common.collect.ImmutableList;
 
 import dagger.Lazy;
@@ -169,6 +170,9 @@
     @Mock
     private VibratorHelper mVibratorHelper;
 
+    @Mock
+    private MSDLPlayer mMSDLPlayer;
+
     private int mLongestHideShowAnimationDuration = 250;
     private FakeSettings mSecureSettings;
 
@@ -222,6 +226,7 @@
                 mDumpManager,
                 mLazySecureSettings,
                 mVibratorHelper,
+                mMSDLPlayer,
                 new FakeSystemClock(),
                 mVolumeDialogInteractor);
         mDialog.init(0, null);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 1b1d8c5..c77d0aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.haptics.msdl.bouncerHapticPlayer
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardMediaKeyInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -60,6 +61,7 @@
         patternViewModelFactory = patternBouncerViewModelFactory,
         passwordViewModelFactory = passwordBouncerViewModelFactory,
         bouncerHapticPlayer = bouncerHapticPlayer,
+        keyguardMediaKeyInteractor = keyguardMediaKeyInteractor,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
index 2b81da3..fe82ab9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -23,19 +23,24 @@
 import com.google.android.msdl.logging.MSDLEvent
 
 class FakeMSDLPlayer : MSDLPlayer {
+    val tokensPlayed = mutableListOf<MSDLToken>()
+    val propertiesPlayed = mutableListOf<InteractionProperties?>()
     private val history = arrayListOf<MSDLEvent>()
+
     var currentFeedbackLevel = FeedbackLevel.DEFAULT
     var latestTokenPlayed: MSDLToken? = null
+        get() = tokensPlayed.lastOrNull()
         private set
 
     var latestPropertiesPlayed: InteractionProperties? = null
+        get() = propertiesPlayed.lastOrNull()
         private set
 
     override fun getSystemFeedbackLevel(): FeedbackLevel = currentFeedbackLevel
 
     override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
-        latestTokenPlayed = token
-        latestPropertiesPlayed = properties
+        tokensPlayed.add(token)
+        propertiesPlayed.add(properties)
         history.add(MSDLEvent(token, properties))
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt
index 257d758..3fbcf77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.interaction.InteractionSource
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.Kosmos
@@ -40,6 +41,7 @@
                     sliderHapticFeedbackConfig,
                     sliderTrackerConfig,
                     vibratorHelper,
+                    msdlPlayer,
                     fakeSystemClock,
                 )
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index fbfaba6..c41493e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -41,7 +41,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.sysUiState
 import com.android.systemui.settings.displayTracker
-import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
 
 var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
     Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) }
@@ -114,7 +114,7 @@
     Kosmos.Fixture {
         ShortcutHelperViewModel(
             mockRoleManager,
-            fakeUserTracker,
+            userTracker,
             applicationCoroutineScope,
             testDispatcher,
             shortcutHelperStateInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index e513e8d..0878649 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -88,9 +88,6 @@
     private val _isDreamingWithOverlay = MutableStateFlow(false)
     override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
 
-    private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
-    override val isActiveDreamLockscreenHosted: StateFlow<Boolean> = _isActiveDreamLockscreenHosted
-
     private val _dozeAmount = MutableStateFlow(0f)
     override val linearDozeAmount: Flow<Float> = _dozeAmount
 
@@ -102,8 +99,7 @@
 
     private val _isUdfpsSupported = MutableStateFlow(false)
 
-    private val _isKeyguardGoingAway = MutableStateFlow(false)
-    override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
+    override val isKeyguardGoingAway = MutableStateFlow(false)
 
     private val _biometricUnlockState =
         MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null))
@@ -169,7 +165,7 @@
     }
 
     fun setKeyguardGoingAway(isGoingAway: Boolean) {
-        _isKeyguardGoingAway.value = isGoingAway
+        isKeyguardGoingAway.value = isGoingAway
     }
 
     fun setKeyguardOccluded(isOccluded: Boolean) {
@@ -235,10 +231,6 @@
         _isDreamingWithOverlay.value = isDreaming
     }
 
-    override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
-        _isActiveDreamLockscreenHosted.value = isLockscreenHosted
-    }
-
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorKosmos.kt
new file mode 100644
index 0000000..6f4787b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.volume.data.repository.audioRepository
+
+val Kosmos.keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor by
+    Kosmos.Fixture {
+        KeyguardMediaKeyInteractor(
+            telephonyInteractor = telephonyInteractor,
+            audioRepository = audioRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
index 8b7e5d8..88063c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.classifier.falsingManager
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.plugins.activityStarter
@@ -31,6 +32,7 @@
             falsingManager,
             uiEventLogger,
             vibratorHelper,
+            msdlPlayer,
             systemClock,
             activityStarter,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 7f4c670..c3996e40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -85,7 +85,6 @@
 import com.android.systemui.util.Assert.runWithCurrentThreadAsMainThread
 import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.wmshell.BubblesManager
@@ -126,6 +125,7 @@
     private val mMainCoroutineContext = mTestScope.coroutineContext
     private val mFakeSystemClock = FakeSystemClock()
     private val mMainExecutor = FakeExecutor(mFakeSystemClock)
+    private val mDumpManager = DumpManager()
 
     init {
         featureFlags.setDefault(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE)
@@ -142,8 +142,7 @@
         mGroupMembershipManager = GroupMembershipManagerImpl()
         mSmartReplyController = Mockito.mock(SmartReplyController::class.java, STUB_ONLY)
 
-        val dumpManager = DumpManager()
-        mGroupExpansionManager = GroupExpansionManagerImpl(dumpManager, mGroupMembershipManager)
+        mGroupExpansionManager = GroupExpansionManagerImpl(mDumpManager, mGroupMembershipManager)
         mHeadsUpManager = Mockito.mock(HeadsUpManager::class.java, STUB_ONLY)
         mIconManager =
             IconManager(
@@ -289,8 +288,8 @@
             NotificationOptimizedLinearLayoutFactory(),
             { Mockito.mock(NotificationViewFlipperFactory::class.java) },
             NotificationRowIconViewInflaterFactory(
-                AppIconProviderImpl(context),
-                NotificationIconStyleProviderImpl(),
+                AppIconProviderImpl(context, mDumpManager),
+                NotificationIconStyleProviderImpl(mDumpManager),
             ),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt
index 08c6bba..0fd0f14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.row.icon
 
 import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.appIconProvider by Kosmos.Fixture { AppIconProviderImpl(applicationContext) }
+val Kosmos.appIconProvider by
+    Kosmos.Fixture { AppIconProviderImpl(applicationContext, dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt
index 611c90a..0fe84fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row.icon
 
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.notificationIconStyleProvider by Kosmos.Fixture { NotificationIconStyleProviderImpl() }
+val Kosmos.notificationIconStyleProvider by
+    Kosmos.Fixture { NotificationIconStyleProviderImpl(dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
index 6be13be..3219127 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
@@ -23,7 +23,7 @@
         listeners -= listener
     }
 
-    override fun onConfigurationChanged(newConfiguration: Configuration?) {
+    override fun onConfigurationChanged(newConfiguration: Configuration) {
         listeners.forEach { it.onConfigChanged(newConfiguration) }
     }
 
@@ -36,7 +36,7 @@
     }
 
     fun notifyConfigurationChanged() {
-        onConfigurationChanged(newConfiguration = null)
+        onConfigurationChanged(newConfiguration = Configuration())
     }
 
     fun notifyLayoutDirectionChanged(isRtl: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
index bca13c6..65247a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
@@ -24,6 +24,6 @@
     override fun create(
         context: Context,
         viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
-        statusBarConfigurationController: StatusBarConfigurationController
+        statusBarConfigurationController: StatusBarConfigurationController,
     ) = FakeStatusBarWindowController()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
index 5cf214a..712ec41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.audioRepository by Kosmos.Fixture { FakeAudioRepository() }
+val Kosmos.fakeAudioRepository by Kosmos.Fixture { FakeAudioRepository() }
+val Kosmos.audioRepository by Kosmos.Fixture { fakeAudioRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index ba6ffd7..16d2a18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -18,6 +18,7 @@
 
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
+import android.view.KeyEvent
 import com.android.settingslib.volume.data.model.VolumeControllerEvent
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.shared.model.AudioStream
@@ -61,6 +62,15 @@
     val isInitialized: Boolean
         get() = mutableIsInitialized
 
+    private val _dispatchedKeyEvents = mutableListOf<KeyEvent>()
+
+    val dispatchedKeyEvents: List<KeyEvent>
+        get() {
+            val currentValue = _dispatchedKeyEvents.toList()
+            _dispatchedKeyEvents.clear()
+            return currentValue
+        }
+
     override fun init() {
         mutableIsInitialized = true
     }
@@ -145,4 +155,8 @@
             mutableIsVolumeControllerVisible.value = isVisible
         }
     }
+
+    override fun dispatchMediaKeyEvent(event: KeyEvent) {
+        _dispatchedKeyEvents.add(event)
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
new file mode 100644
index 0000000..c2a1544
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.volume.dialog.ringer.domain
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+
+val Kosmos.volumeDialogRingerInteractor by
+    Kosmos.Fixture {
+        VolumeDialogRingerInteractor(
+            coroutineScope = applicationCoroutineScope,
+            volumeDialogStateInteractor = volumeDialogStateInteractor,
+            controller = volumeDialogController,
+        )
+    }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index d918201..ff2abd2 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -100,6 +100,9 @@
     srcs: [
         "runtime-helper-src/libcore-fake/**/*.java",
     ],
+    libs: [
+        "app-compat-annotations",
+    ],
     static_libs: [
         "ravenwood-runtime-common",
     ],
@@ -121,6 +124,7 @@
     ],
     static_libs: [
         "ravenwood-runtime-common",
+        "androidx.annotation_annotation",
     ],
     libs: [
         "framework-minus-apex.ravenwood",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
index 3535cb2..870a10a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
@@ -42,6 +42,10 @@
 
     private final RavenwoodConfig mConfig;
 
+    // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by
+    // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry?
+    RavenwoodContext mSystemServerContext;
+
     public RavenwoodConfigState(RavenwoodConfig config) {
         mConfig = config;
     }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 9a145cb..c2806da 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -16,6 +16,8 @@
 
 package android.platform.test.ravenwood;
 
+import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME;
+
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -267,6 +269,13 @@
         config.mInstContext = instContext;
         config.mTargetContext = targetContext;
 
+        final Supplier<Resources> systemResourcesLoader = () -> {
+            return config.mState.loadResources(null);
+        };
+
+        config.mState.mSystemServerContext =
+                new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
+
         // Prepare other fields.
         config.mInstrumentation = new Instrumentation();
         config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
@@ -314,6 +323,9 @@
             ((RavenwoodContext) config.mTargetContext).cleanUp();
             config.mTargetContext = null;
         }
+        if (config.mState.mSystemServerContext != null) {
+            config.mState.mSystemServerContext.cleanUp();
+        }
 
         Looper.getMainLooper().quit();
         Looper.clearMainLooperForTest();
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index 3946dd84..f198a08 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -33,6 +33,9 @@
 import java.util.Set;
 
 public class RavenwoodSystemServer {
+
+    static final String ANDROID_PACKAGE_NAME = "android";
+
     /**
      * Set of services that we know how to provide under Ravenwood. We keep this set distinct
      * from {@code com.android.server.SystemServer} to give us the ability to choose either
@@ -67,7 +70,7 @@
 
         sStartedServices = new ArraySet<>();
         sTimings = new TimingsTraceAndSlog();
-        sServiceManager = new SystemServiceManager(config.mInstContext);
+        sServiceManager = new SystemServiceManager(config.mState.mSystemServerContext);
         sServiceManager.setStartInfo(false,
                 SystemClock.elapsedRealtime(),
                 SystemClock.uptimeMillis());
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 1f6e11d..37b0abc 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -67,6 +67,7 @@
     String mTargetPackageName;
 
     int mMinSdkLevel;
+    int mTargetSdkLevel;
 
     boolean mProvideMainThread = false;
 
@@ -150,6 +151,14 @@
         }
 
         /**
+         * Configure the target SDK level of the test.
+         */
+        public Builder setTargetSdkLevel(int sdkLevel) {
+            mConfig.mTargetSdkLevel = sdkLevel;
+            return this;
+        }
+
+        /**
          * Configure a "main" thread to be available for the duration of the test, as defined
          * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
          *
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index ced1519..9bc45be 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -146,6 +146,9 @@
         if (root.startsWith("soc.")) return true;
         if (root.startsWith("system.")) return true;
 
+        // For PropertyInvalidatedCache
+        if (root.startsWith("cache_key.")) return true;
+
         switch (key) {
             case "gsm.version.baseband":
             case "no.such.thing":
@@ -170,6 +173,9 @@
 
         if (root.startsWith("debug.")) return true;
 
+        // For PropertyInvalidatedCache
+        if (root.startsWith("cache_key.")) return true;
+
         return mKeyWritable.contains(key);
     }
 
diff --git a/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java b/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java
new file mode 100644
index 0000000..1e3b3fc
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java
@@ -0,0 +1,1035 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.util;
+
+// [ravenwood] This is an exact copy from StatsD, until we make StatsD available on Ravenwood.
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Build;
+import android.os.SystemClock;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * StatsEvent builds and stores the buffer sent over the statsd socket.
+ * This class defines and encapsulates the socket protocol.
+ *
+ * <p>Usage:</p>
+ * <pre>
+ *      // Pushed event
+ *      StatsEvent statsEvent = StatsEvent.newBuilder()
+ *          .setAtomId(atomId)
+ *          .writeBoolean(false)
+ *          .writeString("annotated String field")
+ *          .addBooleanAnnotation(annotationId, true)
+ *          .usePooledBuffer()
+ *          .build();
+ *      StatsLog.write(statsEvent);
+ *
+ *      // Pulled event
+ *      StatsEvent statsEvent = StatsEvent.newBuilder()
+ *          .setAtomId(atomId)
+ *          .writeBoolean(false)
+ *          .writeString("annotated String field")
+ *          .addBooleanAnnotation(annotationId, true)
+ *          .build();
+ * </pre>
+ * @hide
+ **/
+@SystemApi
+public final class StatsEvent {
+    // Type Ids.
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_INT = 0x00;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_LONG = 0x01;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_STRING = 0x02;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_LIST = 0x03;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_FLOAT = 0x04;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_BOOLEAN = 0x05;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_BYTE_ARRAY = 0x06;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_OBJECT = 0x07;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_KEY_VALUE_PAIRS = 0x08;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_ERRORS = 0x0F;
+
+    // Error flags.
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_NO_TIMESTAMP = 0x1;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_NO_ATOM_ID = 0x2;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_OVERFLOW = 0x4;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_INVALID_ANNOTATION_ID = 0x40;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_TOO_MANY_FIELDS = 0x200;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting public static final int ERROR_LIST_TOO_LONG = 0x4000;
+
+    // Size limits.
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_ANNOTATION_COUNT = 15;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_ATTRIBUTION_NODES = 127;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_NUM_ELEMENTS = 127;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_KEY_VALUE_PAIRS = 127;
+
+    private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;
+
+    // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
+    // See android_util_StatsLog.cpp.
+    private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
+
+    private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB
+
+    private final int mAtomId;
+    private final byte[] mPayload;
+    private Buffer mBuffer;
+    private final int mNumBytes;
+
+    private StatsEvent(final int atomId, @Nullable final Buffer buffer,
+            @NonNull final byte[] payload, final int numBytes) {
+        mAtomId = atomId;
+        mBuffer = buffer;
+        mPayload = payload;
+        mNumBytes = numBytes;
+    }
+
+    /**
+     * Returns a new StatsEvent.Builder for building StatsEvent object.
+     **/
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder(Buffer.obtain());
+    }
+
+    /**
+     * Get the atom Id of the atom encoded in this StatsEvent object.
+     *
+     * @hide
+     **/
+    public int getAtomId() {
+        return mAtomId;
+    }
+
+    /**
+     * Get the byte array that contains the encoded payload that can be sent to statsd.
+     *
+     * @hide
+     **/
+    @NonNull
+    public byte[] getBytes() {
+        return mPayload;
+    }
+
+    /**
+     * Get the number of bytes used to encode the StatsEvent payload.
+     *
+     * @hide
+     **/
+    public int getNumBytes() {
+        return mNumBytes;
+    }
+
+    /**
+     * Recycle resources used by this StatsEvent object.
+     * No actions should be taken on this StatsEvent after release() is called.
+     *
+     * @hide
+     **/
+    public void release() {
+        if (mBuffer != null) {
+            mBuffer.release();
+            mBuffer = null;
+        }
+    }
+
+    /**
+     * Builder for constructing a StatsEvent object.
+     *
+     * <p>This class defines and encapsulates the socket encoding for the
+     *buffer. The write methods must be called in the same order as the order of
+     *fields in the atom definition.</p>
+     *
+     * <p>setAtomId() must be called immediately after
+     *StatsEvent.newBuilder().</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     *     // Atom definition.
+     *     message MyAtom {
+     *         optional int32 field1 = 1;
+     *         optional int64 field2 = 2;
+     *         optional string field3 = 3 [(annotation1) = true];
+     *         optional repeated int32 field4 = 4;
+     *     }
+     *
+     *     // StatsEvent construction for pushed event.
+     *     StatsEvent.newBuilder()
+     *     StatsEvent statsEvent = StatsEvent.newBuilder()
+     *         .setAtomId(atomId)
+     *         .writeInt(3) // field1
+     *         .writeLong(8L) // field2
+     *         .writeString("foo") // field 3
+     *         .addBooleanAnnotation(annotation1Id, true)
+     *         .writeIntArray({ 1, 2, 3 });
+     *         .usePooledBuffer()
+     *         .build();
+     *
+     *     // StatsEvent construction for pulled event.
+     *     StatsEvent.newBuilder()
+     *     StatsEvent statsEvent = StatsEvent.newBuilder()
+     *         .setAtomId(atomId)
+     *         .writeInt(3) // field1
+     *         .writeLong(8L) // field2
+     *         .writeString("foo") // field 3
+     *         .addBooleanAnnotation(annotation1Id, true)
+     *         .writeIntArray({ 1, 2, 3 });
+     *         .build();
+     * </pre>
+     **/
+    public static final class Builder {
+        // Fixed positions.
+        private static final int POS_NUM_ELEMENTS = 1;
+        private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES;
+        private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES;
+
+        private final Buffer mBuffer;
+        private long mTimestampNs;
+        private int mAtomId;
+        private byte mCurrentAnnotationCount;
+        private int mPos;
+        private int mPosLastField;
+        private byte mLastType;
+        private int mNumElements;
+        private int mErrorMask;
+        private boolean mUsePooledBuffer = false;
+
+        private Builder(final Buffer buffer) {
+            mBuffer = buffer;
+            mCurrentAnnotationCount = 0;
+            mAtomId = 0;
+            mTimestampNs = SystemClock.elapsedRealtimeNanos();
+            mNumElements = 0;
+
+            // Set mPos to 0 for writing TYPE_OBJECT at 0th position.
+            mPos = 0;
+            writeTypeId(TYPE_OBJECT);
+
+            // Write timestamp.
+            mPos = POS_TIMESTAMP_NS;
+            writeLong(mTimestampNs);
+        }
+
+        /**
+         * Sets the atom id for this StatsEvent.
+         *
+         * This should be called immediately after StatsEvent.newBuilder()
+         * and should only be called once.
+         * Not calling setAtomId will result in ERROR_NO_ATOM_ID.
+         * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION.
+         **/
+        @NonNull
+        public Builder setAtomId(final int atomId) {
+            if (0 == mAtomId) {
+                mAtomId = atomId;
+
+                if (1 == mNumElements) { // Only timestamp is written so far.
+                    writeInt(atomId);
+                } else {
+                    // setAtomId called out of order.
+                    mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION;
+                }
+            }
+
+            return this;
+        }
+
+        /**
+         * Write a boolean field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeBoolean(final boolean value) {
+            // Write boolean typeId byte followed by boolean byte representation.
+            writeTypeId(TYPE_BOOLEAN);
+            mPos += mBuffer.putBoolean(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write an integer field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeInt(final int value) {
+            // Write integer typeId byte followed by 4-byte representation of value.
+            writeTypeId(TYPE_INT);
+            mPos += mBuffer.putInt(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write a long field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeLong(final long value) {
+            // Write long typeId byte followed by 8-byte representation of value.
+            writeTypeId(TYPE_LONG);
+            mPos += mBuffer.putLong(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write a float field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeFloat(final float value) {
+            // Write float typeId byte followed by 4-byte representation of value.
+            writeTypeId(TYPE_FLOAT);
+            mPos += mBuffer.putFloat(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write a String field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeString(@NonNull final String value) {
+            // Write String typeId byte, followed by 4-byte representation of number of bytes
+            // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value.
+            final byte[] valueBytes = stringToBytes(value);
+            writeByteArray(valueBytes, TYPE_STRING);
+            return this;
+        }
+
+        /**
+         * Write a byte array field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeByteArray(@NonNull final byte[] value) {
+            // Write byte array typeId byte, followed by 4-byte representation of number of bytes
+            // in value, followed by the actual byte array.
+            writeByteArray(value, TYPE_BYTE_ARRAY);
+            return this;
+        }
+
+        private void writeByteArray(@NonNull final byte[] value, final byte typeId) {
+            writeTypeId(typeId);
+            final int numBytes = value.length;
+            mPos += mBuffer.putInt(mPos, numBytes);
+            mPos += mBuffer.putByteArray(mPos, value);
+            mNumElements++;
+        }
+
+        /**
+         * Write an attribution chain field to this StatsEvent.
+         *
+         * The sizes of uids and tags must be equal. The AttributionNode at position i is
+         * made up of uids[i] and tags[i].
+         *
+         * @param uids array of uids in the attribution nodes.
+         * @param tags array of tags in the attribution nodes.
+         **/
+        @NonNull
+        public Builder writeAttributionChain(
+                @NonNull final int[] uids, @NonNull final String[] tags) {
+            final byte numUids = (byte) uids.length;
+            final byte numTags = (byte) tags.length;
+
+            if (numUids != numTags) {
+                mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL;
+            } else if (numUids > MAX_ATTRIBUTION_NODES) {
+                mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
+            } else {
+                // Write attribution chain typeId byte, followed by 1-byte representation of
+                // number of attribution nodes, followed by encoding of each attribution node.
+                writeTypeId(TYPE_ATTRIBUTION_CHAIN);
+                mPos += mBuffer.putByte(mPos, numUids);
+                for (int i = 0; i < numUids; i++) {
+                    // Each uid is encoded as 4-byte representation of its int value.
+                    mPos += mBuffer.putInt(mPos, uids[i]);
+
+                    // Each tag is encoded as 4-byte representation of number of bytes in its
+                    // UTF-8 encoding, followed by the actual UTF-8 bytes.
+                    final byte[] tagBytes = stringToBytes(tags[i]);
+                    mPos += mBuffer.putInt(mPos, tagBytes.length);
+                    mPos += mBuffer.putByteArray(mPos, tagBytes);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write KeyValuePairsAtom entries to this StatsEvent.
+         *
+         * @param intMap Integer key-value pairs.
+         * @param longMap Long key-value pairs.
+         * @param stringMap String key-value pairs.
+         * @param floatMap Float key-value pairs.
+         **/
+        @NonNull
+        public Builder writeKeyValuePairs(
+                @Nullable final SparseIntArray intMap,
+                @Nullable final SparseLongArray longMap,
+                @Nullable final SparseArray<String> stringMap,
+                @Nullable final SparseArray<Float> floatMap) {
+            final int intMapSize = null == intMap ? 0 : intMap.size();
+            final int longMapSize = null == longMap ? 0 : longMap.size();
+            final int stringMapSize = null == stringMap ? 0 : stringMap.size();
+            final int floatMapSize = null == floatMap ? 0 : floatMap.size();
+            final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
+
+            if (totalCount > MAX_KEY_VALUE_PAIRS) {
+                mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
+            } else {
+                writeTypeId(TYPE_KEY_VALUE_PAIRS);
+                mPos += mBuffer.putByte(mPos, (byte) totalCount);
+
+                for (int i = 0; i < intMapSize; i++) {
+                    final int key = intMap.keyAt(i);
+                    final int value = intMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_INT);
+                    mPos += mBuffer.putInt(mPos, value);
+                }
+
+                for (int i = 0; i < longMapSize; i++) {
+                    final int key = longMap.keyAt(i);
+                    final long value = longMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_LONG);
+                    mPos += mBuffer.putLong(mPos, value);
+                }
+
+                for (int i = 0; i < stringMapSize; i++) {
+                    final int key = stringMap.keyAt(i);
+                    final String value = stringMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_STRING);
+                    final byte[] valueBytes = stringToBytes(value);
+                    mPos += mBuffer.putInt(mPos, valueBytes.length);
+                    mPos += mBuffer.putByteArray(mPos, valueBytes);
+                }
+
+                for (int i = 0; i < floatMapSize; i++) {
+                    final int key = floatMap.keyAt(i);
+                    final float value = floatMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_FLOAT);
+                    mPos += mBuffer.putFloat(mPos, value);
+                }
+
+                mNumElements++;
+            }
+
+            return this;
+        }
+
+        /**
+         * Write a repeated boolean field to this StatsEvent.
+         *
+         * The list size must not exceed 127. Otherwise, the array isn't written
+         * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+         * StatsEvent errors field.
+         *
+         * @param elements array of booleans.
+         **/
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @NonNull
+        public Builder writeBooleanArray(@NonNull final boolean[] elements) {
+            final byte numElements = (byte)elements.length;
+
+            if (writeArrayInfo(numElements, TYPE_BOOLEAN)) {
+                // Write encoding of each element.
+                for (int i = 0; i < numElements; i++) {
+                    mPos += mBuffer.putBoolean(mPos, elements[i]);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write a repeated int field to this StatsEvent.
+         *
+         * The list size must not exceed 127. Otherwise, the array isn't written
+         * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+         * StatsEvent errors field.
+         *
+         * @param elements array of ints.
+         **/
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @NonNull
+        public Builder writeIntArray(@NonNull final int[] elements) {
+            final byte numElements = (byte)elements.length;
+
+            if (writeArrayInfo(numElements, TYPE_INT)) {
+              // Write encoding of each element.
+              for (int i = 0; i < numElements; i++) {
+                mPos += mBuffer.putInt(mPos, elements[i]);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write a repeated long field to this StatsEvent.
+         *
+         * The list size must not exceed 127. Otherwise, the array isn't written
+         * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+         * StatsEvent errors field.
+         *
+         * @param elements array of longs.
+         **/
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @NonNull
+        public Builder writeLongArray(@NonNull final long[] elements) {
+            final byte numElements = (byte)elements.length;
+
+            if (writeArrayInfo(numElements, TYPE_LONG)) {
+                // Write encoding of each element.
+                for (int i = 0; i < numElements; i++) {
+                    mPos += mBuffer.putLong(mPos, elements[i]);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write a repeated float field to this StatsEvent.
+         *
+         * The list size must not exceed 127. Otherwise, the array isn't written
+         * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+         * StatsEvent errors field.
+         *
+         * @param elements array of floats.
+         **/
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @NonNull
+        public Builder writeFloatArray(@NonNull final float[] elements) {
+            final byte numElements = (byte)elements.length;
+
+            if (writeArrayInfo(numElements, TYPE_FLOAT)) {
+                // Write encoding of each element.
+                for (int i = 0; i < numElements; i++) {
+                  mPos += mBuffer.putFloat(mPos, elements[i]);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write a repeated string field to this StatsEvent.
+         *
+         * The list size must not exceed 127. Otherwise, the array isn't written
+         * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+         * StatsEvent errors field.
+         *
+         * @param elements array of strings.
+         **/
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @NonNull
+        public Builder writeStringArray(@NonNull final String[] elements) {
+            final byte numElements = (byte)elements.length;
+
+            if (writeArrayInfo(numElements, TYPE_STRING)) {
+                // Write encoding of each element.
+                for (int i = 0; i < numElements; i++) {
+                    final byte[] elementBytes = stringToBytes(elements[i]);
+                    mPos += mBuffer.putInt(mPos, elementBytes.length);
+                    mPos += mBuffer.putByteArray(mPos, elementBytes);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write a boolean annotation for the last field written.
+         **/
+        @NonNull
+        public Builder addBooleanAnnotation(
+                final byte annotationId, final boolean value) {
+            // Ensure there's a field written to annotate.
+            if (mNumElements < 2) {
+                mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+            } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+                mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+            } else {
+                mPos += mBuffer.putByte(mPos, annotationId);
+                mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN);
+                mPos += mBuffer.putBoolean(mPos, value);
+                mCurrentAnnotationCount++;
+                writeAnnotationCount();
+            }
+
+            return this;
+        }
+
+        /**
+         * Write an integer annotation for the last field written.
+         **/
+        @NonNull
+        public Builder addIntAnnotation(final byte annotationId, final int value) {
+            if (mNumElements < 2) {
+                mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+            } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+                mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+            } else {
+                mPos += mBuffer.putByte(mPos, annotationId);
+                mPos += mBuffer.putByte(mPos, TYPE_INT);
+                mPos += mBuffer.putInt(mPos, value);
+                mCurrentAnnotationCount++;
+                writeAnnotationCount();
+            }
+
+            return this;
+        }
+
+        /**
+         * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent.
+         * This should be called for pushed events to reduce memory allocations and garbage
+         * collections.
+         **/
+        @NonNull
+        public Builder usePooledBuffer() {
+            mUsePooledBuffer = true;
+            mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
+            return this;
+        }
+
+        /**
+         * Builds a StatsEvent object with values entered in this Builder.
+         **/
+        @NonNull
+        public StatsEvent build() {
+            if (0L == mTimestampNs) {
+                mErrorMask |= ERROR_NO_TIMESTAMP;
+            }
+            if (0 == mAtomId) {
+                mErrorMask |= ERROR_NO_ATOM_ID;
+            }
+            if (mBuffer.hasOverflowed()) {
+                mErrorMask |= ERROR_OVERFLOW;
+            }
+            if (mNumElements > MAX_NUM_ELEMENTS) {
+                mErrorMask |= ERROR_TOO_MANY_FIELDS;
+            }
+
+            if (0 == mErrorMask) {
+                mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements);
+            } else {
+                // Write atom id and error mask. Overwrite any annotations for atom Id.
+                mPos = POS_ATOM_ID;
+                mPos += mBuffer.putByte(mPos, TYPE_INT);
+                mPos += mBuffer.putInt(mPos, mAtomId);
+                mPos += mBuffer.putByte(mPos, TYPE_ERRORS);
+                mPos += mBuffer.putInt(mPos, mErrorMask);
+                mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
+            }
+
+            final int size = mPos;
+
+            if (mUsePooledBuffer) {
+                return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
+            } else {
+                // Create a copy of the buffer with the required number of bytes.
+                final byte[] payload = new byte[size];
+                System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size);
+
+                // Return Buffer instance to the pool.
+                mBuffer.release();
+
+                return new StatsEvent(mAtomId, null, payload, size);
+            }
+        }
+
+        private void writeTypeId(final byte typeId) {
+            mPosLastField = mPos;
+            mLastType = typeId;
+            mCurrentAnnotationCount = 0;
+            final byte encodedId = (byte) (typeId & 0x0F);
+            mPos += mBuffer.putByte(mPos, encodedId);
+        }
+
+        private void writeAnnotationCount() {
+            // Use first 4 bits for annotation count and last 4 bits for typeId.
+            final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F));
+            mBuffer.putByte(mPosLastField, encodedId);
+        }
+
+        @NonNull
+        private static byte[] stringToBytes(@Nullable final String value) {
+            return (null == value ? "" : value).getBytes(UTF_8);
+        }
+
+        private boolean writeArrayInfo(final byte numElements,
+                                       final byte elementTypeId) {
+            if (numElements > MAX_NUM_ELEMENTS) {
+                mErrorMask |= ERROR_LIST_TOO_LONG;
+                return false;
+            }
+            // Write list typeId byte, 1-byte representation of number of
+            // elements, and element typeId byte.
+            writeTypeId(TYPE_LIST);
+            mPos += mBuffer.putByte(mPos, numElements);
+            // Write element typeId byte without setting mPosLastField and mLastType (i.e. don't use
+            // #writeTypeId)
+            final byte encodedId = (byte) (elementTypeId & 0x0F);
+            mPos += mBuffer.putByte(mPos, encodedId);
+            return true;
+        }
+    }
+
+    private static final class Buffer {
+        private static Object sLock = new Object();
+
+        @GuardedBy("sLock")
+        private static Buffer sPool;
+
+        private byte[] mBytes;
+        private boolean mOverflow = false;
+        private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
+
+        @NonNull
+        private static Buffer obtain() {
+            final Buffer buffer;
+            synchronized (sLock) {
+                buffer = null == sPool ? new Buffer() : sPool;
+                sPool = null;
+            }
+            buffer.reset();
+            return buffer;
+        }
+
+        private Buffer() {
+            final ByteBuffer tempBuffer = ByteBuffer.allocateDirect(MAX_PUSH_PAYLOAD_SIZE);
+            mBytes = tempBuffer.hasArray() ? tempBuffer.array() : new byte [MAX_PUSH_PAYLOAD_SIZE];
+        }
+
+        @NonNull
+        private byte[] getBytes() {
+            return mBytes;
+        }
+
+        private void release() {
+            // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
+            if (mMaxSize <= MAX_PUSH_PAYLOAD_SIZE) {
+                synchronized (sLock) {
+                    if (null == sPool) {
+                        sPool = this;
+                    }
+                }
+            }
+        }
+
+        private void reset() {
+            mOverflow = false;
+            mMaxSize = MAX_PULL_PAYLOAD_SIZE;
+        }
+
+        private void setMaxSize(final int maxSize, final int numBytesWritten) {
+            mMaxSize = maxSize;
+            if (numBytesWritten > maxSize) {
+                mOverflow = true;
+            }
+        }
+
+        private boolean hasOverflowed() {
+            return mOverflow;
+        }
+
+        /**
+         * Checks for available space in the byte array.
+         *
+         * @param index starting position in the buffer to start the check.
+         * @param numBytes number of bytes to check from index.
+         * @return true if space is available, false otherwise.
+         **/
+        private boolean hasEnoughSpace(final int index, final int numBytes) {
+            final int totalBytesNeeded = index + numBytes;
+
+            if (totalBytesNeeded > mMaxSize) {
+                mOverflow = true;
+                return false;
+            }
+
+            // Expand buffer if needed.
+            if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) {
+                int newSize = mBytes.length;
+                do {
+                    newSize *= 2;
+                } while (newSize <= totalBytesNeeded);
+
+                if (newSize > mMaxSize) {
+                    newSize = mMaxSize;
+                }
+
+                mBytes = Arrays.copyOf(mBytes, newSize);
+            }
+
+            return true;
+        }
+
+        /**
+         * Writes a byte into the buffer.
+         *
+         * @param index position in the buffer where the byte is written.
+         * @param value the byte to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putByte(final int index, final byte value) {
+            if (hasEnoughSpace(index, Byte.BYTES)) {
+                mBytes[index] = (byte) (value);
+                return Byte.BYTES;
+            }
+            return 0;
+        }
+
+        /**
+         * Writes a boolean into the buffer.
+         *
+         * @param index position in the buffer where the boolean is written.
+         * @param value the boolean to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putBoolean(final int index, final boolean value) {
+            return putByte(index, (byte) (value ? 1 : 0));
+        }
+
+        /**
+         * Writes an integer into the buffer.
+         *
+         * @param index position in the buffer where the integer is written.
+         * @param value the integer to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putInt(final int index, final int value) {
+            if (hasEnoughSpace(index, Integer.BYTES)) {
+                // Use little endian byte order.
+                mBytes[index] = (byte) (value);
+                mBytes[index + 1] = (byte) (value >> 8);
+                mBytes[index + 2] = (byte) (value >> 16);
+                mBytes[index + 3] = (byte) (value >> 24);
+                return Integer.BYTES;
+            }
+            return 0;
+        }
+
+        /**
+         * Writes a long into the buffer.
+         *
+         * @param index position in the buffer where the long is written.
+         * @param value the long to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putLong(final int index, final long value) {
+            if (hasEnoughSpace(index, Long.BYTES)) {
+                // Use little endian byte order.
+                mBytes[index] = (byte) (value);
+                mBytes[index + 1] = (byte) (value >> 8);
+                mBytes[index + 2] = (byte) (value >> 16);
+                mBytes[index + 3] = (byte) (value >> 24);
+                mBytes[index + 4] = (byte) (value >> 32);
+                mBytes[index + 5] = (byte) (value >> 40);
+                mBytes[index + 6] = (byte) (value >> 48);
+                mBytes[index + 7] = (byte) (value >> 56);
+                return Long.BYTES;
+            }
+            return 0;
+        }
+
+        /**
+         * Writes a float into the buffer.
+         *
+         * @param index position in the buffer where the float is written.
+         * @param value the float to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putFloat(final int index, final float value) {
+            return putInt(index, Float.floatToIntBits(value));
+        }
+
+        /**
+         * Copies a byte array into the buffer.
+         *
+         * @param index position in the buffer where the byte array is copied.
+         * @param value the byte array to copy.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putByteArray(final int index, @NonNull final byte[] value) {
+            final int numBytes = value.length;
+            if (hasEnoughSpace(index, numBytes)) {
+                System.arraycopy(value, 0, mBytes, index, numBytes);
+                return numBytes;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java b/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java
new file mode 100644
index 0000000..c1c20cf
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/*
+ * [Ravenwood] This is copied from StatsD, with the following changes:
+ * - The static {} is commented out.
+ * - All references to IStatsD and StatsdStatsLog are commented out.
+ * - The native method is no-oped.
+ */
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Build;
+//import android.os.IStatsd;
+import android.os.Process;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.annotation.RequiresApi;
+
+//import com.android.internal.statsd.StatsdStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * StatsLog provides an API for developers to send events to statsd. The events can be used to
+ * define custom metrics in side statsd.
+ */
+public final class StatsLog {
+
+//    // Load JNI library
+//    static {
+//        System.loadLibrary("stats_jni");
+//    }
+    private static final String TAG = "StatsLog";
+    private static final boolean DEBUG = false;
+    private static final int EXPERIMENT_IDS_FIELD_ID = 1;
+
+    /**
+     * Annotation ID constant for logging UID field.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_IS_UID = 1;
+
+    /**
+     * Annotation ID constant to indicate logged atom event's timestamp should be truncated.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
+
+    /**
+     * Annotation ID constant for a state atom's primary field.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3;
+
+    /**
+     * Annotation ID constant for state atom's state field.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4;
+
+    /**
+     * Annotation ID constant to indicate the first UID in the attribution chain
+     * is a primary field.
+     * Should only be used for attribution chain fields.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
+
+    /**
+     * Annotation ID constant to indicate which state is default for the state atom.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_DEFAULT_STATE = 6;
+
+    /**
+     * Annotation ID constant to signal all states should be reset to the default state.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
+
+    /**
+     * Annotation ID constant to indicate state changes need to account for nesting.
+     * This should only be used with binary state atoms.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    public static final byte ANNOTATION_ID_STATE_NESTED = 8;
+
+    /**
+     * Annotation ID constant to indicate the restriction category of an atom.
+     * This annotation must only be attached to the atom id. This is an int annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains peripheral device info.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains app usage information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains app activity information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains health connect information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains accessibility information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains system search information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains user engagement information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains ambient sensing information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains demographic classification
+     * information. This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18;
+
+
+    /** @hide */
+    @IntDef(prefix = { "RESTRICTION_CATEGORY_" }, value = {
+            RESTRICTION_CATEGORY_DIAGNOSTIC,
+            RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE,
+            RESTRICTION_CATEGORY_AUTHENTICATION,
+            RESTRICTION_CATEGORY_FRAUD_AND_ABUSE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RestrictionCategory {}
+
+    /**
+     * Restriction category for atoms about diagnostics.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1;
+
+    /**
+     * Restriction category for atoms about system intelligence.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2;
+
+    /**
+     * Restriction category for atoms about authentication.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3;
+
+    /**
+     * Restriction category for atoms about fraud and abuse.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4;
+
+    private StatsLog() {
+    }
+
+    /**
+     * Logs a start event.
+     *
+     * @param label developer-chosen label.
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logStart(int label) {
+        int callingUid = Process.myUid();
+//        StatsdStatsLog.write(
+//                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+//                callingUid,
+//                label,
+//                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+        return true;
+    }
+
+    /**
+     * Logs a stop event.
+     *
+     * @param label developer-chosen label.
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logStop(int label) {
+        int callingUid = Process.myUid();
+//        StatsdStatsLog.write(
+//                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+//                callingUid,
+//                label,
+//                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+        return true;
+    }
+
+    /**
+     * Logs an event that does not represent a start or stop boundary.
+     *
+     * @param label developer-chosen label.
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logEvent(int label) {
+        int callingUid = Process.myUid();
+//        StatsdStatsLog.write(
+//                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+//                callingUid,
+//                label,
+//                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+        return true;
+    }
+
+    /**
+     * Logs an event for binary push for module updates.
+     *
+     * @param trainName        name of install train.
+     * @param trainVersionCode version code of the train.
+     * @param options          optional flags about this install.
+     *                         The last 3 bits indicate options:
+     *                             0x01: FLAG_REQUIRE_STAGING
+     *                             0x02: FLAG_ROLLBACK_ENABLED
+     *                             0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR
+     * @param state            current install state. Defined as State enums in
+     *                         BinaryPushStateChanged atom in
+     *                         frameworks/proto_logging/stats/atoms.proto
+     * @param experimentIds    experiment ids.
+     * @return True if the log request was sent to statsd.
+     */
+    @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
+    public static boolean logBinaryPushStateChanged(@NonNull String trainName,
+            long trainVersionCode, int options, int state,
+            @NonNull long[] experimentIds) {
+        ProtoOutputStream proto = new ProtoOutputStream();
+        for (long id : experimentIds) {
+            proto.write(
+                    ProtoOutputStream.FIELD_TYPE_INT64
+                    | ProtoOutputStream.FIELD_COUNT_REPEATED
+                    | EXPERIMENT_IDS_FIELD_ID,
+                    id);
+        }
+//        StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED,
+//                trainName,
+//                trainVersionCode,
+//                (options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
+//                (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0,
+//                (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0,
+//                state,
+//                proto.getBytes(),
+//                0,
+//                0,
+//                false);
+        return true;
+    }
+
+    /**
+     * Write an event to stats log using the raw format.
+     *
+     * @param buffer    The encoded buffer of data to write.
+     * @param size      The number of bytes from the buffer to write.
+     * @hide
+     * @deprecated Use {@link write(final StatsEvent statsEvent)} instead.
+     *
+     */
+    @Deprecated
+    @SystemApi
+    public static void writeRaw(@NonNull byte[] buffer, int size) {
+        writeImpl(buffer, size, 0);
+    }
+
+    /**
+     * Write an event to stats log using the raw format.
+     *
+     * @param buffer    The encoded buffer of data to write.
+     * @param size      The number of bytes from the buffer to write.
+     * @param atomId    The id of the atom to which the event belongs.
+     */
+//    private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId);
+    private static void writeImpl(@NonNull byte[] buffer, int size, int atomId) {
+        // no-op for now
+    }
+
+    /**
+     * Write an event to stats log using the raw format encapsulated in StatsEvent.
+     * After writing to stats log, release() is called on the StatsEvent object.
+     * No further action should be taken on the StatsEvent object following this call.
+     *
+     * @param statsEvent    The StatsEvent object containing the encoded buffer of data to write.
+     * @hide
+     */
+    @SystemApi
+    public static void write(@NonNull final StatsEvent statsEvent) {
+        writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
+        statsEvent.release();
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java b/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java
new file mode 100644
index 0000000..c737684
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.compat;
+
+// [Ravenwood] Copied from libcore, with "RAVENWOOD-CHANGE"
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+
+import libcore.api.IntraCoreApi;
+import libcore.util.NonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Internal APIs for logging and gating compatibility changes.
+ *
+ * @see ChangeId
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+@IntraCoreApi
+public final class Compatibility {
+
+    private Compatibility() {}
+
+    /**
+     * Reports that a compatibility change is affecting the current process now.
+     *
+     * <p>Calls to this method from a non-app process are ignored. This allows code implementing
+     * APIs that are used by apps and by other code (e.g. the system server) to report changes
+     * regardless of the process it's running in. When called in a non-app process, this method is
+     * a no-op.
+     *
+     * <p>Note: for changes that are gated using {@link #isChangeEnabled(long)}, you do not need to
+     * call this API directly. The change will be reported for you in the case that
+     * {@link #isChangeEnabled(long)} returns {@code true}.
+     *
+     * @param changeId The ID of the compatibility change taking effect.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @IntraCoreApi
+    public static void reportUnconditionalChange(@ChangeId long changeId) {
+        sCallbacks.onChangeReported(changeId);
+    }
+
+    /**
+     * Query if a given compatibility change is enabled for the current process. This method should
+     * only be called by code running inside a process of the affected app.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method returns
+     * {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * <p>When this method returns {@code true}, it will also report the change as
+     * {@link #reportUnconditionalChange(long)} would, so there is no need to call that method
+     * directly.
+     *
+     * @param changeId The ID of the compatibility change in question.
+     * @return {@code true} if the change is enabled for the current app.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @IntraCoreApi
+    public static boolean isChangeEnabled(@ChangeId long changeId) {
+        return sCallbacks.isChangeEnabled(changeId);
+    }
+
+    private static final BehaviorChangeDelegate DEFAULT_CALLBACKS = new BehaviorChangeDelegate(){};
+
+    private volatile static BehaviorChangeDelegate sCallbacks = DEFAULT_CALLBACKS;
+
+    /**
+     * Sets the behavior change delegate.
+     *
+     * All changes reported via the {@link Compatibility} class will be forwarded to this class.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void setBehaviorChangeDelegate(BehaviorChangeDelegate callbacks) {
+        sCallbacks = Objects.requireNonNull(callbacks);
+    }
+
+    /**
+     * Removes a behavior change delegate previously set via {@link #setBehaviorChangeDelegate}.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void clearBehaviorChangeDelegate() {
+        sCallbacks = DEFAULT_CALLBACKS;
+    }
+
+    /**
+     * Return the behavior change delegate
+     *
+     * @hide
+     */
+    // VisibleForTesting
+    @NonNull
+    public static BehaviorChangeDelegate getBehaviorChangeDelegate() {
+        return sCallbacks;
+    }
+
+    /**
+     * For use by tests only. Causes values from {@code overrides} to be returned instead of the
+     * real value.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void setOverrides(ChangeConfig overrides) {
+        // Setting overrides twice in a row does not need to be supported because
+        // this method is only for enabling/disabling changes for the duration of
+        // a single test.
+        // In production, the app is restarted when changes get enabled or disabled,
+        // and the ChangeConfig is then set exactly once on that app process.
+        if (sCallbacks instanceof OverrideCallbacks) {
+            throw new IllegalStateException("setOverrides has already been called!");
+        }
+        sCallbacks = new OverrideCallbacks(sCallbacks, overrides);
+    }
+
+    /**
+     * For use by tests only. Removes overrides set by {@link #setOverrides}.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void clearOverrides() {
+        if (!(sCallbacks instanceof OverrideCallbacks)) {
+            throw new IllegalStateException("No overrides set");
+        }
+        sCallbacks = ((OverrideCallbacks) sCallbacks).delegate;
+    }
+
+    /**
+     * Base class for compatibility API implementations. The default implementation logs a warning
+     * to logcat.
+     *
+     * This is provided as a class rather than an interface to allow new methods to be added without
+     * breaking @SystemApi binary compatibility.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public interface BehaviorChangeDelegate {
+        /**
+         * Called when a change is reported via {@link Compatibility#reportUnconditionalChange}
+         *
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        default void onChangeReported(long changeId) {
+            // Do not use String.format here (b/160912695)
+
+            // RAVENWOOD-CHANGE
+            System.out.println("No Compatibility callbacks set! Reporting change " + changeId);
+        }
+
+        /**
+         * Called when a change is queried via {@link Compatibility#isChangeEnabled}
+         *
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        default boolean isChangeEnabled(long changeId) {
+            // Do not use String.format here (b/160912695)
+            // TODO(b/289900411): Rate limit this log if it's necessary in the release build.
+            // System.logW("No Compatibility callbacks set! Querying change " + changeId);
+            return true;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @IntraCoreApi
+    public static final class ChangeConfig {
+        private final Set<Long> enabled;
+        private final Set<Long> disabled;
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public ChangeConfig(@NonNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled) {
+            this.enabled = Objects.requireNonNull(enabled);
+            this.disabled = Objects.requireNonNull(disabled);
+            if (enabled.contains(null)) {
+                throw new NullPointerException();
+            }
+            if (disabled.contains(null)) {
+                throw new NullPointerException();
+            }
+            Set<Long> intersection = new HashSet<>(enabled);
+            intersection.retainAll(disabled);
+            if (!intersection.isEmpty()) {
+                throw new IllegalArgumentException("Cannot have changes " + intersection
+                        + " enabled and disabled!");
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public boolean isEmpty() {
+            return enabled.isEmpty() && disabled.isEmpty();
+        }
+
+        private static long[] toLongArray(Set<Long> values) {
+            long[] result = new long[values.size()];
+            int idx = 0;
+            for (Long value: values) {
+                result[idx++] = value;
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public @NonNull long[] getEnabledChangesArray() {
+            return toLongArray(enabled);
+        }
+
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public @NonNull long[] getDisabledChangesArray() {
+            return toLongArray(disabled);
+        }
+
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public @NonNull Set<@NonNull Long> getEnabledSet() {
+            return Collections.unmodifiableSet(enabled);
+        }
+
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public @NonNull Set<@NonNull Long> getDisabledSet() {
+            return Collections.unmodifiableSet(disabled);
+        }
+
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public boolean isForceEnabled(long changeId) {
+            return enabled.contains(changeId);
+        }
+
+
+        /**
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        @IntraCoreApi
+        public boolean isForceDisabled(long changeId) {
+            return disabled.contains(changeId);
+        }
+
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ChangeConfig)) {
+                return false;
+            }
+            ChangeConfig that = (ChangeConfig) o;
+            return enabled.equals(that.enabled) &&
+                    disabled.equals(that.disabled);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public int hashCode() {
+            return Objects.hash(enabled, disabled);
+        }
+
+
+        /**
+         * @hide
+         */
+        @Override
+        public String toString() {
+            return "ChangeConfig{enabled=" + enabled + ", disabled=" + disabled + '}';
+        }
+    }
+
+    private static class OverrideCallbacks implements BehaviorChangeDelegate {
+        private final BehaviorChangeDelegate delegate;
+        private final ChangeConfig changeConfig;
+
+        private OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig) {
+            this.delegate = Objects.requireNonNull(delegate);
+            this.changeConfig = Objects.requireNonNull(changeConfig);
+        }
+        @Override
+        public boolean isChangeEnabled(long changeId) {
+           if (changeConfig.isForceEnabled(changeId)) {
+               return true;
+           }
+           if (changeConfig.isForceDisabled(changeId)) {
+               return false;
+           }
+           return delegate.isChangeEnabled(changeId);
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java
new file mode 100644
index 0000000..00730ef
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is part of a contract provided by the "core" set of
+ * libraries to select parts of the Android software stack.
+ *
+ * <p>This annotation should only appear on either (a) classes that are hidden by <pre>@hide</pre>
+ * javadoc tags or equivalent annotations, or (b) members of such classes. It is for use with
+ * metalava's {@code --show-single-annotation} option and so must be applied at the class level and
+ * applied again each member that is to be made part of the API. Members that are not part of the
+ * API do not have to be explicitly hidden.
+ *
+ * @hide
+ */
+@IntraCoreApi
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface CorePlatformApi {
+
+    /** Enumeration of the possible statuses of the API in the core/platform API surface. */
+    @IntraCoreApi
+    enum Status {
+
+        /**
+         * This API is considered stable, and so present in both the stable and legacy version of
+         * the API surface.
+        */
+        @IntraCoreApi
+        STABLE,
+
+        /**
+         * This API is not (yet) considered stable, and so only present in the legacy version of
+         * the API surface.
+         */
+        @IntraCoreApi
+        LEGACY_ONLY
+    }
+
+    /** The status of the API in the core/platform API surface. */
+    @IntraCoreApi
+    Status status() default Status.LEGACY_ONLY;
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java
new file mode 100644
index 0000000..f87ff11d
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that an API is hidden by default, in a similar fashion to the
+ * <pre>@hide</pre> javadoc tag.
+ *
+ * <p>Note that, in order for this to work, metalava has to be invoked with
+ * the flag {@code --hide-annotation libcore.api.Hide}.
+ *
+ * <p>This annotation should be used in {@code .annotated.java} stub files which
+ * contain API inclusion information about {@code libcore/ojluni} classes, to
+ * avoid patching the source files with <pre>@hide</pre> javadoc tags. All
+ * build targets which consume these stub files should also apply the above
+ * metalava flag.
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Hide {
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java
new file mode 100644
index 0000000..87cfcff2
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is part of a contract within the "core" set of libraries, some of which may
+ * be mmodules.
+ *
+ * <p>This annotation should only appear on either (a) classes that are hidden by <pre>@hide</pre>
+ * javadoc tags or equivalent annotations, or (b) members of such classes. It is for use with
+ * metalava's {@code --show-single-annotation} option and so must be applied at the class level and
+ * applied again each member that is to be made part of the API. Members that are not part of the
+ * API do not have to be explicitly hidden.
+ *
+ * @hide
+ */
+@IntraCoreApi // @IntraCoreApi is itself part of the intra-core API
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface IntraCoreApi {
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
new file mode 100644
index 0000000..db3cd8ed
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.util;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a type use can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
+@libcore.api.IntraCoreApi
+public @interface NonNull {
+   /**
+    * Min Android API level (inclusive) to which this annotation is applied.
+    */
+   int from() default Integer.MIN_VALUE;
+
+   /**
+    * Max Android API level to which this annotation is applied.
+    */
+   int to() default Integer.MAX_VALUE;
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
new file mode 100644
index 0000000..3371978
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.util;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a type use can be a null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
+@libcore.api.IntraCoreApi
+public @interface Nullable {
+   /**
+    * Min Android API level (inclusive) to which this annotation is applied.
+    */
+   int from() default Integer.MIN_VALUE;
+
+   /**
+    * Max Android API level to which this annotation is applied.
+    */
+   int to() default Integer.MAX_VALUE;
+}
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 3649f0e..b64944e 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -5,6 +5,10 @@
 rename com/.*/nano/   devicenano/
 rename android/.*/nano/   devicenano/
 
+
+# StatsD autogenerated classes. Maybe add a heuristic?
+class com.android.internal.util.FrameworkStatsLog keepclass
+
 # Exported to Mainline modules; cannot use annotations
 class com.android.internal.util.FastXmlSerializer keepclass
 class com.android.internal.util.FileRotator keepclass
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a77ba62..ce1a292 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -64,6 +64,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.Flags;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.ArrayList;
@@ -396,7 +397,7 @@
                             mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
                         sendSpecToAnimation(mCurrentMagnificationSpec, null);
                     }
-                    onMagnificationChangedLocked();
+                    onMagnificationChangedLocked(/* isScaleTransient= */ false);
                 }
                 magnified.recycle();
             }
@@ -474,8 +475,16 @@
             return mIdOfLastServiceToMagnify;
         }
 
+        /**
+         * This is invoked whenever magnification change happens.
+         *
+         * @param isScaleTransient represents that if the scale is being changed and the changed
+         *                         value may be short lived and be updated again soon.
+         *                         Calling the method usually notifies input manager to update the
+         *                         cursor scale, but setting this value {@code true} prevents it.
+         */
         @GuardedBy("mLock")
-        void onMagnificationChangedLocked() {
+        void onMagnificationChangedLocked(boolean isScaleTransient) {
             final float scale = getScale();
             final float centerX = getCenterX();
             final float centerY = getCenterY();
@@ -498,6 +507,10 @@
             } else {
                 hideThumbnail();
             }
+
+            if (!isScaleTransient) {
+                notifyScaleForInput(mDisplayId, scale);
+            }
         }
 
         @GuardedBy("mLock")
@@ -611,8 +624,9 @@
          * Directly Zooms out the scale to 1f with animating the transition. This method is
          * triggered only by service automatically, such as when user context changed.
          */
+        @GuardedBy("mLock")
         void zoomOutFromService() {
-            setScaleAndCenter(1.0f, Float.NaN, Float.NaN,
+            setScaleAndCenter(1.0f, Float.NaN, Float.NaN, /* isScaleTransient= */ false,
                     transformToStubCallback(true),
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             mZoomedOutFromService = true;
@@ -640,7 +654,7 @@
             setActivated(false);
             if (changed) {
                 spec.clear();
-                onMagnificationChangedLocked();
+                onMagnificationChangedLocked(/* isScaleTransient= */ false);
             }
             mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
             sendSpecToAnimation(spec, animationCallback);
@@ -651,7 +665,7 @@
         }
 
         @GuardedBy("mLock")
-        boolean setScale(float scale, float pivotX, float pivotY,
+        boolean setScale(float scale, float pivotX, float pivotY, boolean isScaleTransient,
                 boolean animate, int id) {
             if (!mRegistered) {
                 return false;
@@ -674,12 +688,14 @@
             final float centerX = normPivotX + offsetX;
             final float centerY = normPivotY + offsetY;
             mIdOfLastServiceToMagnify = id;
-            return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id);
+            return setScaleAndCenter(scale, centerX, centerY, isScaleTransient,
+                    transformToStubCallback(animate), id);
         }
 
         @GuardedBy("mLock")
         boolean setScaleAndCenter(float scale, float centerX, float centerY,
-                MagnificationAnimationCallback animationCallback, int id) {
+                boolean isScaleTransient, MagnificationAnimationCallback animationCallback,
+                int id) {
             if (!mRegistered) {
                 return false;
             }
@@ -696,7 +712,7 @@
                                 + animationCallback + ", id = " + id + ")");
             }
             boolean changed = setActivated(true);
-            changed |= updateMagnificationSpecLocked(scale, centerX, centerY);
+            changed |= updateMagnificationSpecLocked(scale, centerX, centerY, isScaleTransient);
             sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
             if (isActivated() && (id != INVALID_SERVICE_ID)) {
                 mIdOfLastServiceToMagnify = id;
@@ -773,7 +789,9 @@
          * @return {@code true} if the magnification spec changed or {@code false}
          *         otherwise
          */
-        boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+        @GuardedBy("mLock")
+        boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY,
+                boolean isScaleTransient) {
             // Handle defaults.
             if (Float.isNaN(centerX)) {
                 centerX = getCenterX();
@@ -801,7 +819,7 @@
             changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
 
             if (changed) {
-                onMagnificationChangedLocked();
+                onMagnificationChangedLocked(isScaleTransient);
             }
 
             return changed;
@@ -816,7 +834,7 @@
             final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
             final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
             if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
-                onMagnificationChangedLocked();
+                onMagnificationChangedLocked(/* isScaleTransient= */ false);
             }
             if (id != INVALID_SERVICE_ID) {
                 mIdOfLastServiceToMagnify = id;
@@ -861,7 +879,7 @@
                             }
                             synchronized (mLock) {
                                 mCurrentMagnificationSpec.setTo(lastSpecSent);
-                                onMagnificationChangedLocked();
+                                onMagnificationChangedLocked(/* isScaleTransient= */ false);
                             }
                         }
                     });
@@ -955,6 +973,7 @@
                         context,
                         traceManager,
                         LocalServices.getService(WindowManagerInternal.class),
+                        LocalServices.getService(InputManagerInternal.class),
                         new Handler(context.getMainLooper()),
                         context.getResources().getInteger(R.integer.config_longAnimTime)),
                 lock,
@@ -1464,20 +1483,24 @@
      * @param scale the target scale, must be >= 1
      * @param pivotX the screen-relative X coordinate around which to scale
      * @param pivotY the screen-relative Y coordinate around which to scale
+     * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+     *                         soon. {@code false} otherwise.
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
      * @param id the ID of the service requesting the change
      * @return {@code true} if the magnification spec changed, {@code false} if
      *         the spec did not change
      */
+    @SuppressWarnings("GuardedBy")
+    // errorprone cannot recognize an inner class guarded by an outer class member.
     public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
-            boolean animate, int id) {
+            boolean isScaleTransient, boolean animate, int id) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
             if (display == null) {
                 return false;
             }
-            return display.setScale(scale, pivotX, pivotY, animate, id);
+            return display.setScale(scale, pivotX, pivotY, isScaleTransient, animate, id);
         }
     }
 
@@ -1496,6 +1519,8 @@
      * @return {@code true} if the magnification spec changed, {@code false} if
      * the spec did not change
      */
+    @SuppressWarnings("GuardedBy")
+    // errorprone cannot recognize an inner class guarded by an outer class member.
     public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
@@ -1503,7 +1528,7 @@
                 return false;
             }
             return display.setScaleAndCenter(Float.NaN, centerX, centerY,
-                    animate ? STUB_ANIMATION_CALLBACK : null, id);
+                    /* isScaleTransient= */ false, animate ? STUB_ANIMATION_CALLBACK : null, id);
         }
     }
 
@@ -1526,7 +1551,32 @@
      */
     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
             boolean animate, int id) {
-        return setScaleAndCenter(displayId, scale, centerX, centerY,
+        return setScaleAndCenter(displayId, scale, centerX, centerY, /* isScaleTransient= */ false,
+                transformToStubCallback(animate), id);
+    }
+
+    /**
+     * Sets the scale and center of the magnified region, optionally
+     * animating the transition. If animation is disabled, the transition
+     * is immediate.
+     *
+     * @param displayId        The logical display id.
+     * @param scale            the target scale, or {@link Float#NaN} to leave unchanged
+     * @param centerX          the screen-relative X coordinate around which to
+     *                         center and scale, or {@link Float#NaN} to leave unchanged
+     * @param centerY          the screen-relative Y coordinate around which to
+     *                         center and scale, or {@link Float#NaN} to leave unchanged
+     * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+     *                         soon. {@code false} otherwise.
+     * @param animate          {@code true} to animate the transition, {@code false}
+     *                         to transition immediately
+     * @param id               the ID of the service requesting the change
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     * the spec did not change
+     */
+    public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+            boolean isScaleTransient, boolean animate, int id) {
+        return setScaleAndCenter(displayId, scale, centerX, centerY, isScaleTransient,
                 transformToStubCallback(animate), id);
     }
 
@@ -1541,20 +1591,25 @@
      *                center and scale, or {@link Float#NaN} to leave unchanged
      * @param centerY the screen-relative Y coordinate around which to
      *                center and scale, or {@link Float#NaN} to leave unchanged
+     * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+     *                         soon. {@code false} otherwise.
      * @param animationCallback Called when the animation result is valid.
      *                           {@code null} to transition immediately
      * @param id the ID of the service requesting the change
      * @return {@code true} if the magnification spec changed, {@code false} if
      *         the spec did not change
      */
+    @SuppressWarnings("GuardedBy")
+    // errorprone cannot recognize an inner class guarded by an outer class member.
     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
-            MagnificationAnimationCallback animationCallback, int id) {
+            boolean isScaleTransient, MagnificationAnimationCallback animationCallback, int id) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
             if (display == null) {
                 return false;
             }
-            return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id);
+            return display.setScaleAndCenter(scale, centerX, centerY, isScaleTransient,
+                    animationCallback, id);
         }
     }
 
@@ -1569,6 +1624,8 @@
      *                screen pixels.
      * @param id      the ID of the service requesting the change
      */
+    @SuppressWarnings("GuardedBy")
+    // errorprone cannot recognize an inner class guarded by an outer class member.
     public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
@@ -1640,6 +1697,8 @@
      */
     public void persistScale(int displayId) {
         final float scale = getScale(displayId);
+        notifyScaleForInput(displayId, scale);
+
         if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
             return;
         }
@@ -1665,6 +1724,8 @@
      *
      * @param displayId The logical display id.
      */
+    @SuppressWarnings("GuardedBy")
+    // errorprone cannot recognize an inner class guarded by an outer class member.
     private void zoomOutFromService(int displayId) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
@@ -1691,6 +1752,20 @@
     }
 
     /**
+     * Notifies input manager that magnification scale changed non-transiently
+     * so that pointer cursor is scaled as well.
+     *
+     * @param displayId The logical display id.
+     * @param scale     The new scale factor.
+     */
+    public void notifyScaleForInput(int displayId, float scale) {
+        if (Flags.magnificationEnlargePointer()) {
+            mControllerCtx.getInputManager()
+                    .setAccessibilityPointerIconScaleFactor(displayId, scale);
+        }
+    }
+
+    /**
      * Resets all displays' magnification if last magnifying service is disabled.
      *
      * @param connectionId
@@ -2166,6 +2241,7 @@
         private final Context mContext;
         private final AccessibilityTraceManager mTrace;
         private final WindowManagerInternal mWindowManager;
+        private final InputManagerInternal mInputManager;
         private final Handler mHandler;
         private final Long mAnimationDuration;
 
@@ -2175,11 +2251,13 @@
         public ControllerContext(@NonNull Context context,
                 @NonNull AccessibilityTraceManager traceManager,
                 @NonNull WindowManagerInternal windowManager,
+                @NonNull InputManagerInternal inputManager,
                 @NonNull Handler handler,
                 long animationDuration) {
             mContext = context;
             mTrace = traceManager;
             mWindowManager = windowManager;
+            mInputManager = inputManager;
             mHandler = handler;
             mAnimationDuration = animationDuration;
         }
@@ -2209,6 +2287,14 @@
         }
 
         /**
+         * @return InputManagerInternal
+         */
+        @NonNull
+        public InputManagerInternal getInputManager() {
+            return mInputManager;
+        }
+
+        /**
          * @return Handler for main looper
          */
         @NonNull
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 963334b..c6a966f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -617,7 +617,8 @@
             }
 
             if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
-            mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+            mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY,
+                    /* isScaleTransient= */ true, /* animate= */ false,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
 
             checkShouldDetectPassPersistedScale();
@@ -1974,6 +1975,7 @@
                     /* scale= */ scale,
                     /* centerX= */ mPivotEdge.x,
                     /* centerY= */ mPivotEdge.y,
+                    /* isScaleTransient= */ true,
                     /* animate= */ true,
                     /* id= */ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             if (scale == 1.0f) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 1489d16..d40e747 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -176,7 +176,8 @@
     public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
         if (getFullScreenMagnificationController().isActivated(displayId)) {
             getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
-                    Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
+                    Float.NaN, Float.NaN, /* isScaleTransient= */ !updatePersistence, false,
+                    MAGNIFICATION_GESTURE_HANDLER_ID);
             if (updatePersistence) {
                 getFullScreenMagnificationController().persistScale(displayId);
             }
@@ -371,7 +372,7 @@
                         }
                         screenMagnificationController.setScaleAndCenter(displayId, targetScale,
                                 magnificationCenter.x, magnificationCenter.y,
-                                magnificationAnimationCallback, id);
+                                /* isScaleTransient= */ false, magnificationAnimationCallback, id);
                     } else {
                         if (screenMagnificationController.isRegistered(displayId)) {
                             screenMagnificationController.reset(displayId, false);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 95281c8..5911070 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -715,11 +715,17 @@
 
         @Override
         public byte[] getBackupPayload(int userId) {
+            if (getCallingUid() != SYSTEM_UID) {
+                throw new SecurityException("Caller must be system");
+            }
             return mBackupRestoreProcessor.getBackupPayload(userId);
         }
 
         @Override
         public void applyRestoredPayload(byte[] payload, int userId) {
+            if (getCallingUid() != SYSTEM_UID) {
+                throw new SecurityException("Caller must be system");
+            }
             mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
         }
 
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 39ac515..363807d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -68,6 +68,7 @@
 import android.telephony.DisconnectCause;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.LocationAccessPolicy;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.PhysicalChannelConfig;
@@ -90,6 +91,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.LocalLog;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -429,6 +431,8 @@
     private boolean[] mCarrierRoamingNtnMode = null;
     private boolean[] mCarrierRoamingNtnEligible = null;
 
+    private List<IntArray> mCarrierRoamingNtnAvailableServices;
+
     /**
      * Per-phone map of precise data connection state. The key of the map is the pair of transport
      * type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -741,6 +745,7 @@
                 cutListToSize(mCarrierServiceStates, mNumPhones);
                 cutListToSize(mCallStateLists, mNumPhones);
                 cutListToSize(mMediaQualityStatus, mNumPhones);
+                cutListToSize(mCarrierRoamingNtnAvailableServices, mNumPhones);
                 return;
             }
 
@@ -789,6 +794,7 @@
                 mSCBMDuration[i] = 0;
                 mCarrierRoamingNtnMode[i] = false;
                 mCarrierRoamingNtnEligible[i] = false;
+                mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
             }
         }
     }
@@ -864,6 +870,7 @@
         mSCBMDuration = new long[numPhones];
         mCarrierRoamingNtnMode = new boolean[numPhones];
         mCarrierRoamingNtnEligible = new boolean[numPhones];
+        mCarrierRoamingNtnAvailableServices = new ArrayList<>();
 
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
@@ -909,6 +916,7 @@
             mSCBMDuration[i] = 0;
             mCarrierRoamingNtnMode[i] = false;
             mCarrierRoamingNtnEligible[i] = false;
+            mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1533,6 +1541,15 @@
                         remove(r.binder);
                     }
                 }
+                if (events.contains(
+                        TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED)) {
+                    try {
+                        r.callback.onCarrierRoamingNtnAvailableServicesChanged(
+                                mCarrierRoamingNtnAvailableServices.get(r.phoneId).toArray());
+                    } catch (RemoteException ex) {
+                        remove(r.binder);
+                    }
+                }
             }
         }
     }
@@ -3642,6 +3659,47 @@
         }
     }
 
+    /**
+     * Notify external listeners that carrier roaming non-terrestrial available services changed.
+     * @param availableServices The list of the supported services.
+     */
+    public void notifyCarrierRoamingNtnAvailableServicesChanged(
+            int subId, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+        if (!checkNotifyPermission("notifyCarrierRoamingNtnEligibleStateChanged")) {
+            log("notifyCarrierRoamingNtnAvailableServicesChanged: caller does not have required "
+                    + "permissions.");
+            return;
+        }
+
+        if (VDBG) {
+            log("notifyCarrierRoamingNtnAvailableServicesChanged: "
+                    + "availableServices=" + Arrays.toString(availableServices));
+        }
+
+        synchronized (mRecords) {
+            int phoneId = getPhoneIdFromSubId(subId);
+            if (!validatePhoneId(phoneId)) {
+                loge("Invalid phone ID " + phoneId + " for " + subId);
+                return;
+            }
+            IntArray availableServicesIntArray = new IntArray(availableServices.length);
+            availableServicesIntArray.addAll(availableServices);
+            mCarrierRoamingNtnAvailableServices.set(phoneId, availableServicesIntArray);
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(
+                        TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED)
+                        && idMatch(r, subId, phoneId)) {
+                    try {
+                        r.callback.onCarrierRoamingNtnAvailableServicesChanged(availableServices);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3706,6 +3764,8 @@
                 Pair<String, Integer> carrierServiceState = mCarrierServiceStates.get(i);
                 pw.println("mCarrierServiceState=<package=" + pii(carrierServiceState.first)
                         + ", uid=" + carrierServiceState.second + ">");
+                pw.println("mCarrierRoamingNtnAvailableServices="
+                        + mCarrierRoamingNtnAvailableServices.get(i));
                 pw.decreaseIndent();
             }
 
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 947f6b7..51c768b 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -75,6 +75,7 @@
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Slog;
@@ -82,7 +83,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.LocationPermissionChecker;
 import com.android.net.module.util.PermissionUtils;
 import com.android.server.vcn.TelephonySubscriptionTracker;
@@ -448,7 +449,7 @@
         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
         final UserManager userManager = mContext.getSystemService(UserManager.class);
 
-        Binder.withCleanCallingIdentity(
+        BinderUtils.withCleanCallingIdentity(
                 () -> {
                     if (!Objects.equals(userManager.getMainUser(), userHandle)) {
                         throw new SecurityException(
@@ -468,7 +469,7 @@
         // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker
         final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
         final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>();
-        Binder.withCleanCallingIdentity(
+        BinderUtils.withCleanCallingIdentity(
                 () -> {
                     List<SubscriptionInfo> subsInGroup =
                             subMgr.getSubscriptionsInGroup(subscriptionGroup);
@@ -700,7 +701,7 @@
     @GuardedBy("mLock")
     private void notifyAllPolicyListenersLocked() {
         for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
-            Binder.withCleanCallingIdentity(() -> {
+            BinderUtils.withCleanCallingIdentity(() -> {
                 try {
                     policyListener.mListener.onPolicyChanged();
                 } catch (RemoteException e) {
@@ -715,7 +716,7 @@
             @NonNull ParcelUuid subGroup, @VcnStatusCode int statusCode) {
         for (final VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
             if (isCallbackPermissioned(cbInfo, subGroup)) {
-                Binder.withCleanCallingIdentity(() -> {
+                BinderUtils.withCleanCallingIdentity(() -> {
                     try {
                         cbInfo.mCallback.onVcnStatusChanged(statusCode);
                     } catch (RemoteException e) {
@@ -795,7 +796,7 @@
         enforceManageTestNetworksForTestMode(config);
         enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
 
-        Binder.withCleanCallingIdentity(() -> {
+        BinderUtils.withCleanCallingIdentity(() -> {
             synchronized (mLock) {
                 mConfigs.put(subscriptionGroup, config);
                 startOrUpdateVcnLocked(subscriptionGroup, config);
@@ -853,7 +854,7 @@
                 .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
         enforceCarrierPrivilegeOrProvisioningPackage(subscriptionGroup, opPkgName);
 
-        Binder.withCleanCallingIdentity(() -> {
+        BinderUtils.withCleanCallingIdentity(() -> {
             synchronized (mLock) {
                 stopAndClearVcnConfigInternalLocked(subscriptionGroup);
                 writeConfigsToDiskLocked();
@@ -991,7 +992,7 @@
                 android.Manifest.permission.NETWORK_FACTORY,
                 android.Manifest.permission.MANAGE_TEST_NETWORKS);
 
-        Binder.withCleanCallingIdentity(() -> {
+        BinderUtils.withCleanCallingIdentity(() -> {
             PolicyListenerBinderDeath listenerBinderDeath = new PolicyListenerBinderDeath(listener);
 
             synchronized (mLock) {
@@ -1018,7 +1019,7 @@
                 android.Manifest.permission.NETWORK_FACTORY,
                 android.Manifest.permission.MANAGE_TEST_NETWORKS);
 
-        Binder.withCleanCallingIdentity(() -> {
+        BinderUtils.withCleanCallingIdentity(() -> {
             synchronized (mLock) {
                 PolicyListenerBinderDeath listenerBinderDeath =
                         mRegisteredPolicyListeners.remove(listener.asBinder());
@@ -1082,7 +1083,7 @@
                             + " MANAGE_TEST_NETWORKS");
         }
 
-        return Binder.withCleanCallingIdentity(() -> {
+        return BinderUtils.withCleanCallingIdentity(() -> {
             // Defensive copy in case this call is in-process and the given NetworkCapabilities
             // mutates
             final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities);
@@ -1521,7 +1522,7 @@
                 // Notify all registered StatusCallbacks for this subGroup
                 for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
                     if (isCallbackPermissioned(cbInfo, mSubGroup)) {
-                        Binder.withCleanCallingIdentity(() -> {
+                        BinderUtils.withCleanCallingIdentity(() -> {
                             try {
                                 cbInfo.mCallback.onGatewayConnectionError(
                                         gatewayConnectionName,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 217ef20..6ba8514 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,6 +60,7 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
+import static android.content.Intent.isPreventIntentRedirectEnabled;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
 import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
 import static android.content.pm.PackageManager.MATCH_ALL;
@@ -130,7 +131,6 @@
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
-import static android.security.Flags.preventIntentRedirect;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -19272,7 +19272,7 @@
      * @hide
      */
     public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
-        if (!preventIntentRedirect()) return;
+        if (!isPreventIntentRedirectEnabled()) return;
 
         if (intent == null || intent.getExtraIntentKeys() == null) return;
         for (String key : intent.getExtraIntentKeys()) {
@@ -19283,7 +19283,9 @@
                             + "} does not correspond to an intent in the extra bundle.");
                     continue;
                 }
-                Slog.wtf(TAG, "A creator token is added to an intent.");
+                Slog.wtf(TAG,
+                        "A creator token is added to an intent. creatorPackage: " + creatorPackage
+                                + "; intent: " + intent);
                 IBinder creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
                 if (creatorToken != null) {
                     extraIntent.setCreatorToken(creatorToken);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c47cad9..28b606c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -3178,12 +3178,15 @@
             mStats.collectPowerStatsSamples();
         }
 
-        BatteryUsageStats batteryUsageStats =
-                mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query);
-        if (proto) {
-            batteryUsageStats.dumpToProto(fd);
-        } else {
-            batteryUsageStats.dump(pw, "  ");
+        try (BatteryUsageStats batteryUsageStats =
+                     mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query)) {
+            if (proto) {
+                batteryUsageStats.dumpToProto(fd);
+            } else {
+                batteryUsageStats.dump(pw, "  ");
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Cannot close BatteryUsageStats", e);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index ba4b71c..17fcbf4 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.am.ProcessRecord.TAG;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -58,7 +58,7 @@
 import com.android.modules.expresslog.Counter;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 1d68ee54..83b0801 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -67,6 +67,10 @@
             mNormalBrightnessModeController.resetNbmData(
                     displayDeviceConfig.getLuxThrottlingData());
         }
+        if (flags.useNewHdrBrightnessModifier()) {
+            // HDR boost is handled by HdrBrightnessModifier and should be disabled in HbmController
+            mHbmController.disableHdrBoost();
+        }
         updateHdrClamper(info, displayToken, displayDeviceConfig);
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 42a62f0..5b61f00 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1503,7 +1503,6 @@
                 // use the current brightness setting scaled by the doze scale factor
                 rawBrightnessState = getDozeBrightnessForOffload();
                 brightnessState = clampScreenBrightness(rawBrightnessState);
-                updateScreenBrightnessSetting = false;
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
                 mTempBrightnessEvent.setFlags(
                         mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
@@ -1513,6 +1512,7 @@
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
             }
+            updateScreenBrightnessSetting = false;
         }
 
         if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 135cab6..6be0c12 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -38,6 +38,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.display.DisplayManagerService.Clock;
 import com.android.server.display.config.HighBrightnessModeData;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
@@ -119,6 +120,14 @@
     @Nullable
     private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
 
+    /**
+     * If {@link DisplayManagerFlags#useNewHdrBrightnessModifier()} is ON, hdr boost is handled by
+     * {@link com.android.server.display.brightness.clamper.HdrBrightnessModifier} and should be
+     * disabled in this class. After flag is cleaned up, this field together with HDR handling
+     * should be cleaned up from this class.
+     */
+    private boolean mHdrBoostDisabled = false;
+
     HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
             String displayUniqueId, float brightnessMin, float brightnessMax,
             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
@@ -323,6 +332,7 @@
         pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
         pw.println("  width*height=" + mWidth + "*" + mHeight);
+        pw.println("  mHdrBoostDisabled=" + mHdrBoostDisabled);
 
         if (mHighBrightnessModeMetadata != null) {
             pw.println("  mRunningStartTimeMillis="
@@ -373,6 +383,11 @@
         return mHbmData != null && mHighBrightnessModeMetadata != null;
     }
 
+    void disableHdrBoost() {
+        mHdrBoostDisabled = true;
+        unregisterHdrListener();
+    }
+
     private long calculateRemainingTime(long currentTime) {
         if (!deviceSupportsHbm()) {
             return 0;
@@ -583,6 +598,9 @@
     }
 
     private void registerHdrListener(IBinder displayToken) {
+        if (mHdrBoostDisabled) {
+            return;
+        }
         if (mRegisteredDisplayToken == displayToken) {
             return;
         }
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 99f7f12..c888eef 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -262,4 +262,12 @@
      */
     public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes,
             int modifierState, @KeyGestureEvent.KeyGestureType int event);
+
+    /**
+     * Sets the magnification scale factor for pointer icons.
+     *
+     * @param displayId   the ID of the display where the new scale factor is applied.
+     * @param scaleFactor the new scale factor to be applied for pointer icons.
+     */
+    public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8acf583..98e5319 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3506,6 +3506,11 @@
                 int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) {
             mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType);
         }
+
+        @Override
+        public void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
+            InputManagerService.this.setAccessibilityPointerIconScaleFactor(displayId, scaleFactor);
+        }
     }
 
     @Override
@@ -3688,6 +3693,10 @@
         mPointerIconCache.setPointerScale(scale);
     }
 
+    void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
+        mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
+    }
+
     interface KeyboardBacklightControllerInterface {
         default void incrementKeyboardBacklight(int deviceId) {}
         default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index b488db5..2f5236f 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -23,6 +23,7 @@
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
@@ -654,6 +655,18 @@
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_D:
+                if (enableMoveToNextDisplayShortcut()) {
+                    if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed()) {
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
diff --git a/services/core/java/com/android/server/input/PointerIconCache.java b/services/core/java/com/android/server/input/PointerIconCache.java
index 297cd68..e16031c 100644
--- a/services/core/java/com/android/server/input/PointerIconCache.java
+++ b/services/core/java/com/android/server/input/PointerIconCache.java
@@ -27,6 +27,7 @@
 import android.os.Handler;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseDoubleArray;
 import android.util.SparseIntArray;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
@@ -34,6 +35,7 @@
 import android.view.PointerIcon;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.UiThread;
 
 import java.util.Objects;
@@ -51,7 +53,7 @@
     private final NativeInputManagerService mNative;
 
     // We use the UI thread for loading pointer icons.
-    private final Handler mUiThreadHandler = UiThread.getHandler();
+    private final Handler mUiThreadHandler;
 
     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
     private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
@@ -70,6 +72,9 @@
             POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
     private float mPointerIconScale = DEFAULT_POINTER_SCALE;
+    // Note that android doesn't have SparseFloatArray, so this falls back to use double instead.
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private final SparseDoubleArray mAccessibilityScaleFactorPerDisplay = new SparseDoubleArray();
 
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
@@ -86,6 +91,7 @@
                         mLoadedPointerIconsByDisplayAndType.remove(displayId);
                         mDisplayContexts.remove(displayId);
                         mDisplayDensities.delete(displayId);
+                        mAccessibilityScaleFactorPerDisplay.delete(displayId);
                     }
                 }
 
@@ -96,8 +102,15 @@
             };
 
     /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
+        this(context, nativeService, UiThread.getHandler());
+    }
+
+    @VisibleForTesting
+    /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService,
+            Handler handler) {
         mContext = context;
         mNative = nativeService;
+        mUiThreadHandler = handler;
     }
 
     public void systemRunning() {
@@ -134,6 +147,11 @@
         mUiThreadHandler.post(() -> handleSetPointerScale(scale));
     }
 
+    /** Set the scale for accessibility (magnification) for vector pointer icons. */
+    public void setAccessibilityScaleFactor(int displayId, float scaleFactor) {
+        mUiThreadHandler.post(() -> handleAccessibilityScaleFactor(displayId, scaleFactor));
+    }
+
     /**
      * Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
      * it isn't already cached.
@@ -155,8 +173,10 @@
                         /* force= */ true);
                 theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(mPointerIconStrokeStyle),
                         /* force= */ true);
+                final float scale = mPointerIconScale
+                        * (float) mAccessibilityScaleFactorPerDisplay.get(displayId, 1f);
                 icon = PointerIcon.getLoadedSystemIcon(new ContextThemeWrapper(context, theme),
-                        type, mUseLargePointerIcons, mPointerIconScale);
+                        type, mUseLargePointerIcons, scale);
                 iconsByType.put(type, icon);
             }
             return Objects.requireNonNull(icon);
@@ -261,6 +281,19 @@
         mNative.reloadPointerIcons();
     }
 
+    @android.annotation.UiThread
+    private void handleAccessibilityScaleFactor(int displayId, float scale) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            if (mAccessibilityScaleFactorPerDisplay.get(displayId, 1f) == scale) {
+                return;
+            }
+            mAccessibilityScaleFactorPerDisplay.put(displayId, scale);
+            // Clear cached icons on the display.
+            mLoadedPointerIconsByDisplayAndType.remove(displayId);
+        }
+        mNative.reloadPointerIcons();
+    }
+
     // Updates the cached display density for the given displayId, and returns true if
     // the cached density changed.
     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index d1576c5..509fa3e 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -127,42 +127,18 @@
     @BinderThread
     public void updateRuleSet(
             String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver) {
-        String ruleProvider = getCallerPackageNameOrThrow(Binder.getCallingUid());
-        if (DEBUG_INTEGRITY_COMPONENT) {
-            Slog.i(TAG, String.format("Calling rule provider name is: %s.", ruleProvider));
+        Intent intent = new Intent();
+        intent.putExtra(EXTRA_STATUS, STATUS_SUCCESS);
+        try {
+            statusReceiver.sendIntent(
+                mContext,
+                /* code= */ 0,
+                intent,
+                /* onFinished= */ null,
+                /* handler= */ null);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error sending status feedback.", e);
         }
-
-        mHandler.post(
-                () -> {
-                    boolean success = true;
-                    try {
-                        mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
-                    } catch (Exception e) {
-                        Slog.e(TAG, "Error writing rules.", e);
-                        success = false;
-                    }
-
-                    if (DEBUG_INTEGRITY_COMPONENT) {
-                        Slog.i(
-                                TAG,
-                                String.format(
-                                        "Successfully pushed rule set to version '%s' from '%s'",
-                                        version, ruleProvider));
-                    }
-
-                    Intent intent = new Intent();
-                    intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
-                    try {
-                        statusReceiver.sendIntent(
-                                mContext,
-                                /* code= */ 0,
-                                intent,
-                                /* onFinished= */ null,
-                                /* handler= */ null);
-                    } catch (Exception e) {
-                        Slog.e(TAG, "Error sending status feedback.", e);
-                    }
-                });
     }
 
     @Override
@@ -209,21 +185,6 @@
                 verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
     }
 
-    /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
-    private String getPackageNameNormalized(String packageName) {
-        if (packageName.length() <= 32) {
-            return packageName;
-        }
-
-        try {
-            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
-            byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
-            return getHexDigest(hashBytes);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("SHA-256 algorithm not found", e);
-        }
-    }
-
     private String getCallerPackageNameOrThrow(int callingUid) {
         String callerPackageName = getCallingRulePusherPackageName(callingUid);
         if (callerPackageName == null) {
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index ea4a6db..25741bc 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -81,6 +81,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import com.android.internal.annotations.GuardedBy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -152,6 +153,8 @@
     @interface MuteReason {}
 
     private final Context mContext;
+    //This is NMS.mNotificationLock.
+    private final Object mLock;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
     private final UserManager mUm;
@@ -165,6 +168,7 @@
 
     private VibratorHelper mVibratorHelper;
     // The last key in this list owns the hardware.
+    @GuardedBy("mLock")
     ArrayList<String> mLights = new ArrayList<>();
     private LogicalLight mNotificationLight;
     private LogicalLight mAttentionLight;
@@ -183,8 +187,10 @@
     private String mVibrateNotificationKey;
     private boolean mSystemReady;
     private boolean mInCallStateOffHook = false;
+    @GuardedBy("mLock")
     private boolean mScreenOn = true;
     private boolean mUserPresent = false;
+    @GuardedBy("mLock")
     private boolean mNotificationPulseEnabled;
     private final Uri mInCallNotificationUri;
     private final AudioAttributes mInCallNotificationAudioAttributes;
@@ -200,12 +206,13 @@
     private final PolitenessStrategy mStrategy;
     private int mCurrentWorkProfileId = UserHandle.USER_NULL;
 
-    public NotificationAttentionHelper(Context context, LightsManager lightsManager,
+    public NotificationAttentionHelper(Context context, Object lock, LightsManager lightsManager,
             AccessibilityManager accessibilityManager, PackageManager packageManager,
             UserManager userManager, NotificationUsageStats usageStats,
             NotificationManagerPrivate notificationManagerPrivate,
             ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
         mContext = context;
+        mLock = lock;
         mPackageManager = packageManager;
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mAccessibilityManager = accessibilityManager;
@@ -368,9 +375,11 @@
     private void loadUserSettings() {
         boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
                 Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
-        if (mNotificationPulseEnabled != pulseEnabled) {
-            mNotificationPulseEnabled = pulseEnabled;
-            updateLightsLocked();
+        synchronized (mLock) {
+            if (mNotificationPulseEnabled != pulseEnabled) {
+                mNotificationPulseEnabled = pulseEnabled;
+                updateLightsLocked();
+            }
         }
 
         if (Flags.politeNotifications()) {
@@ -1148,7 +1157,8 @@
         }
     }
 
-    public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
+    public void dumpLocked(PrintWriter pw, String prefix,
+            NotificationManagerService.DumpFilter filter) {
         pw.println("\n  Notification attention state:");
         pw.print(prefix);
         pw.println("  mSoundNotificationKey=" + mSoundNotificationKey);
@@ -1684,16 +1694,22 @@
             if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 // Keep track of screen on/off state, but do not turn off the notification light
                 // until user passes through the lock screen or views the notification.
-                mScreenOn = true;
-                updateLightsLocked();
+                synchronized (mLock) {
+                    mScreenOn = true;
+                    updateLightsLocked();
+                }
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                mScreenOn = false;
-                mUserPresent = false;
-                updateLightsLocked();
+                synchronized (mLock) {
+                    mScreenOn = false;
+                    mUserPresent = false;
+                    updateLightsLocked();
+                }
             } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
                 mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK
                         .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE));
-                updateLightsLocked();
+                synchronized (mLock) {
+                    updateLightsLocked();
+                }
             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
                 mUserPresent = true;
                 // turn off LED when user passes through lock screen
@@ -1755,9 +1771,11 @@
                         Settings.System.NOTIFICATION_LIGHT_PULSE, 0,
                         UserHandle.USER_CURRENT)
                         != 0;
-                if (mNotificationPulseEnabled != pulseEnabled) {
-                    mNotificationPulseEnabled = pulseEnabled;
-                    updateLightsLocked();
+                synchronized (mLock) {
+                    if (mNotificationPulseEnabled != pulseEnabled) {
+                        mNotificationPulseEnabled = pulseEnabled;
+                        updateLightsLocked();
+                    }
                 }
             }
             if (Flags.politeNotifications()) {
@@ -1840,7 +1858,9 @@
 
     @VisibleForTesting
     void setScreenOn(boolean on) {
-        mScreenOn = on;
+        synchronized (mLock) {
+            mScreenOn = on;
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e48c8ee..48cc032 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2640,9 +2640,9 @@
 
         mToastRateLimiter = toastRateLimiter;
 
-        mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
-                mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
-                mNotificationManagerPrivate, mZenModeHelper, flagResolver);
+        mAttentionHelper = new NotificationAttentionHelper(getContext(), mNotificationLock,
+                lightsManager, mAccessibilityManager, mPackageManagerClient, userManager,
+                usageStats, mNotificationManagerPrivate, mZenModeHelper, flagResolver);
 
         // register for various Intents.
         // If this is called within a test, make sure to unregister the intent receivers by
@@ -7331,7 +7331,7 @@
                     pw.println("  mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
                     pw.println("  hideSilentStatusBar="
                             + mPreferencesHelper.shouldHideSilentStatusIcons());
-                    mAttentionHelper.dump(pw, "    ", filter);
+                    mAttentionHelper.dumpLocked(pw, "    ", filter);
                 }
                 pw.println("  mArchive=" + mArchive.toString());
                 mArchive.dumpImpl(pw, filter);
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index a221152..d1d6ed0 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -34,16 +34,19 @@
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
 import android.media.audiopolicy.AudioPolicy;
+import android.multiuser.Flags;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
+import java.util.Set;
 
 public class BackgroundUserSoundNotifier {
 
@@ -65,6 +68,10 @@
      */
     @VisibleForTesting
     int mNotificationClientUid = -1;
+    /**
+     * UIDs of audio focus infos with active notifications.
+     */
+    Set<Integer> mNotificationClientUids = new ArraySet<>();
     @VisibleForTesting
     AudioPolicy mFocusControlAudioPolicy;
     @VisibleForTesting
@@ -149,26 +156,42 @@
             @SuppressLint("MissingPermission")
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (mNotificationClientUid == -1) {
-                    return;
+                if (Flags.multipleAlarmNotificationsSupport()) {
+                    if (!intent.hasExtra(EXTRA_NOTIFICATION_CLIENT_UID)) {
+                        return;
+                    }
+                } else {
+                    if (mNotificationClientUid == -1) {
+                        return;
+                    }
                 }
-                dismissNotification(mNotificationClientUid);
+
+                int clientUid;
+                if (Flags.multipleAlarmNotificationsSupport()) {
+                    clientUid = intent.getIntExtra(EXTRA_NOTIFICATION_CLIENT_UID, -1);
+                } else {
+                    clientUid = mNotificationClientUid;
+                }
+                dismissNotification(clientUid);
 
                 if (DEBUG) {
                     final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
                     final String action = intent.getAction().substring(actionIndex);
                     Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
                             + ActivityManager.getCurrentUser() + " for alarm on user "
-                            + UserHandle.getUserHandleForUid(mNotificationClientUid));
+                            + UserHandle.getUserHandleForUid(clientUid));
                 }
 
                 if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
-                    muteAlarmSounds(mNotificationClientUid);
+                    muteAlarmSounds(clientUid);
                 } else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
-                    activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
+                    activityManager.switchUser(UserHandle.getUserId(clientUid));
                 }
-
-                mNotificationClientUid = -1;
+                if (Flags.multipleAlarmNotificationsSupport()) {
+                    mNotificationClientUids.remove(clientUid);
+                } else {
+                    mNotificationClientUid = -1;
+                }
             }
         };
 
@@ -215,10 +238,11 @@
         final int userId = UserHandle.getUserId(afi.getClientUid());
         final int usage = afi.getAttributes().getUsage();
         UserInfo userInfo = mUserManager.getUserInfo(userId);
+
         // Only show notification if the sound is coming from background user and the notification
-        // is not already shown.
+        // for this UID is not already shown.
         if (userInfo != null && userId != foregroundContext.getUserId()
-                && mNotificationClientUid == -1) {
+                && !isNotificationShown(afi.getClientUid())) {
             //TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
             if (usage == USAGE_ALARM) {
                 if (DEBUG) {
@@ -226,8 +250,11 @@
                             + ", displaying notification for current user "
                             + foregroundContext.getUserId());
                 }
-
-                mNotificationClientUid = afi.getClientUid();
+                if (Flags.multipleAlarmNotificationsSupport()) {
+                    mNotificationClientUids.add(afi.getClientUid());
+                } else {
+                    mNotificationClientUid = afi.getClientUid();
+                }
 
                 mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
                         createNotification(userInfo.name, foregroundContext, afi.getClientUid()),
@@ -243,15 +270,21 @@
      */
     @VisibleForTesting
     void dismissNotificationIfNecessary(int notificationClientUid) {
+
         if (getAudioFocusInfoForNotification(notificationClientUid) == null
-                && mNotificationClientUid >= 0) {
+                && isNotificationShown(notificationClientUid)) {
             if (DEBUG) {
                 Log.d(LOG_TAG, "Alarm ringing on background user "
                         + UserHandle.getUserHandleForUid(notificationClientUid).getIdentifier()
                         + " left focus stack, dismissing notification");
             }
             dismissNotification(notificationClientUid);
-            mNotificationClientUid = -1;
+
+            if (Flags.multipleAlarmNotificationsSupport()) {
+                mNotificationClientUids.remove(notificationClientUid);
+            } else {
+                mNotificationClientUid = -1;
+            }
         }
     }
 
@@ -331,4 +364,12 @@
 
         return notificationBuilder.build();
     }
+
+    private boolean isNotificationShown(int notificationClientUid) {
+        if (Flags.multipleAlarmNotificationsSupport()) {
+            return mNotificationClientUids.contains(notificationClientUid);
+        } else {
+            return mNotificationClientUid != -1;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index f69a017..35a69a2 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -22,6 +22,8 @@
 import android.app.IAlarmManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.PowerManagerInternal;
@@ -32,6 +34,9 @@
 import android.util.SparseArray;
 import android.view.Display;
 
+import com.android.server.LocalServices;
+import com.android.server.pm.pkg.AndroidPackage;
+
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -266,11 +271,18 @@
                         ServiceManager.getService(Context.ALARM_SERVICE));
             }
             try {
-                // This command is called by the shell, which has "com.android.shell" as package
-                // name.
-                pw.println("Schedule an alarm to wakeup in "
-                        + delayMillis + " ms, on behalf of shell.");
-                mAlarmManager.set("com.android.shell",
+                PackageManagerInternal packageManagerInternal =
+                        LocalServices.getService(PackageManagerInternal.class);
+                AndroidPackage callingPackage =
+                        packageManagerInternal.getPackage(Binder.getCallingUid());
+                if (callingPackage == null) {
+                    pw.println("Calling uid " + Binder.getCallingUid() + " is not an android"
+                        + " package. Cannot schedule a delayed wakeup on behalf of it.");
+                    return -1;
+                }
+                pw.println("Schedule an alarm to wakeup in " + delayMillis +
+                        " ms, on behalf of " + callingPackage.getPackageName());
+                mAlarmManager.set(callingPackage.getPackageName(),
                         AlarmManager.RTC_WAKEUP, wakeUpTime,
                         0, 0, AlarmManager.FLAG_PRIORITIZE,
                         null, mAlarmListener, "PowerManagerShellCommand", null, null);
diff --git a/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
index dd6d5db..c0a06fc 100644
--- a/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
+++ b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
@@ -51,6 +51,11 @@
         mBatteryUsageStats.build().dump(ipw, "");
     }
 
+    @Override
+    public void close() {
+        mBatteryUsageStats.discard();
+    }
+
     static class Reader implements PowerStatsSpan.SectionReader {
         @Override
         public String getType() {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index c04158f..48174a6 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11509,9 +11509,6 @@
                     mOnBatteryTimeBase);
         }
 
-        mPerDisplayBatteryStats = new DisplayBatteryStats[1];
-        mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
-
         mInteractiveTimer = new StopwatchTimer(mClock, null, -10, null, mOnBatteryTimeBase);
         mPowerSaveModeEnabledTimer = new StopwatchTimer(mClock, null, -2, null,
                 mOnBatteryTimeBase);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 265f1dfc..b996c43 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -295,7 +295,8 @@
                     stats.builder = ((AccumulatedBatteryUsageStatsSection) section)
                             .getBatteryUsageStatsBuilder();
                     stats.startWallClockTime = powerStatsSpan.getMetadata().getStartTime();
-                    stats.startMonotonicTime = powerStatsSpan.getMetadata().getStartMonotonicTime();
+                    stats.startMonotonicTime =
+                            powerStatsSpan.getMetadata().getStartMonotonicTime();
                     stats.endMonotonicTime = powerStatsSpan.getMetadata().getEndMonotonicTime();
                     break;
                 }
@@ -484,29 +485,30 @@
                 continue;
             }
 
-            PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
-                    spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
-            if (powerStatsSpan == null) {
-                continue;
-            }
-
-            for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
-                BatteryUsageStats snapshot =
-                        ((BatteryUsageStatsSection) section).getBatteryUsageStats();
-                if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
-                        customEnergyConsumerNames)) {
-                    Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
-                            + "custom power components: "
-                            + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+            try (PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+                    spanMetadata.getId(), BatteryUsageStatsSection.TYPE)) {
+                if (powerStatsSpan == null) {
                     continue;
                 }
 
-                if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
-                    Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
-                            + " does not include process state data");
-                    continue;
+                for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+                    BatteryUsageStats snapshot =
+                            ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+                    if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
+                            customEnergyConsumerNames)) {
+                        Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+                                + "custom power components: "
+                                + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+                        continue;
+                    }
+
+                    if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
+                        Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
+                                + " does not include process state data");
+                        continue;
+                    }
+                    builder.add(snapshot);
                 }
-                builder.add(snapshot);
             }
         }
         return builder.build();
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
index af36524..eb896e9 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -18,6 +18,7 @@
 
 import android.os.BatteryUsageStats;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -28,6 +29,7 @@
 
 class BatteryUsageStatsSection extends PowerStatsSpan.Section {
     public static final String TYPE = "battery-usage-stats";
+    private static final String TAG = "BatteryUsageStatsSection";
 
     private final BatteryUsageStats mBatteryUsageStats;
 
@@ -50,6 +52,15 @@
         mBatteryUsageStats.dump(ipw, "");
     }
 
+    @Override
+    public void close() {
+        try {
+            mBatteryUsageStats.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Closing BatteryUsageStats", e);
+        }
+    }
+
     static class Reader implements PowerStatsSpan.SectionReader {
         @Override
         public String getType() {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
index 5105272..4f560cf 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -51,7 +51,7 @@
 /**
  * Contains power stats of various kinds, aggregated over a time span.
  */
-public class PowerStatsSpan {
+public class PowerStatsSpan implements AutoCloseable {
     private static final String TAG = "PowerStatsStore";
 
     /**
@@ -321,6 +321,13 @@
         public void dump(IndentingPrintWriter ipw) {
             ipw.println(mType);
         }
+
+        /**
+         * Closes the section, releasing any resources it held. Once closed, the Section
+         * should not be used.
+         */
+        public void close() {
+        }
     }
 
     /**
@@ -484,4 +491,10 @@
             ipw.decreaseIndent();
         }
     }
+    @Override
+    public void close() {
+        for (int i = 0; i < mSections.size(); i++) {
+            mSections.get(i).close();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
index 3673617..d83d355 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -331,9 +331,10 @@
         ipw.increaseIndent();
         List<PowerStatsSpan.Metadata> contents = getTableOfContents();
         for (PowerStatsSpan.Metadata metadata : contents) {
-            PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
-            if (span != null) {
-                span.dump(ipw);
+            try (PowerStatsSpan span = loadPowerStatsSpan(metadata.getId())) {
+                if (span != null) {
+                    span.dump(ipw);
+                }
             }
         }
         ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
index 32dfdf9..5f93bdf 100644
--- a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
@@ -50,12 +50,14 @@
             return;
         }
 
-        if (mScreenPowerStatsDescriptor == null) {
-            mScreenPowerStatsDescriptor = screenStats.getPowerStatsDescriptor();
-            if (mScreenPowerStatsDescriptor == null) {
-                return;
-            }
+        PowerStats.Descriptor screenDescriptor = screenStats.getPowerStatsDescriptor();
+        if (screenDescriptor == null) {
+            return;
+        }
 
+        if (mScreenPowerStatsDescriptor == null
+                || !mScreenPowerStatsDescriptor.equals(screenDescriptor)) {
+            mScreenPowerStatsDescriptor = screenDescriptor;
             mScreenPowerStatsLayout = new ScreenPowerStatsLayout(mScreenPowerStatsDescriptor);
             mTmpScreenStats = new long[mScreenPowerStatsDescriptor.statsArrayLength];
         }
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index c8170a1..b2442c8 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -105,19 +105,19 @@
                     maxEndTime = spanMaxTime;
                 }
 
-                PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
-                        AggregatedPowerStatsSection.TYPE);
-                if (span == null) {
-                    Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
-                    continue;
-                }
-                List<PowerStatsSpan.Section> sections = span.getSections();
-                for (int k = 0; k < sections.size(); k++) {
-                    hasStoredSpans = true;
-                    PowerStatsSpan.Section section = sections.get(k);
-                    populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
-                            ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
-                    // TODO(b/371614748): close the builder
+                try (PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+                        AggregatedPowerStatsSection.TYPE)) {
+                    if (span == null) {
+                        Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
+                        continue;
+                    }
+                    List<PowerStatsSpan.Section> sections = span.getSections();
+                    for (int k = 0; k < sections.size(); k++) {
+                        hasStoredSpans = true;
+                        PowerStatsSpan.Section section = sections.get(k);
+                        populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
+                                ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
+                    }
                 }
             }
 
diff --git a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
index b295e30..8e7498f 100644
--- a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
@@ -89,14 +89,15 @@
             return true;
         }
 
-        mLastUsedDescriptor = descriptor;
-        mStatsLayout = new ScreenPowerStatsLayout(descriptor);
-        if (mStatsLayout.getDisplayCount() != mDisplayCount) {
-            Slog.e(TAG, "Incompatible number of displays: " + mStatsLayout.getDisplayCount()
+        ScreenPowerStatsLayout statsLayout = new ScreenPowerStatsLayout(descriptor);
+        if (statsLayout.getDisplayCount() != mDisplayCount) {
+            Slog.e(TAG, "Incompatible number of displays: " + statsLayout.getDisplayCount()
                     + ", expected: " + mDisplayCount);
             return false;
         }
 
+        mLastUsedDescriptor = descriptor;
+        mStatsLayout = statsLayout;
         mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
         mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
         return true;
diff --git a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java b/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java
new file mode 100644
index 0000000..caca011
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.forensic;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.forensic.ForensicEvent;
+import android.security.forensic.IBackupTransport;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BackupTransportConnection implements ServiceConnection {
+    private static final String TAG = "BackupTransportConnection";
+    private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
+    private final Context mContext;
+    private String mForensicBackupTransportConfig;
+    volatile IBackupTransport mService;
+
+    public BackupTransportConnection(Context context) {
+        mContext = context;
+        mService = null;
+    }
+
+    /**
+     * Initialize the BackupTransport binder service.
+     * @return Whether the initialization succeed.
+     */
+    public boolean initialize() {
+        if (!bindService()) {
+            return false;
+        }
+        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        try {
+            mService.initialize(resultFuture);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote Exception", e);
+            unbindService();
+            return false;
+        }
+        Integer result = getFutureResult(resultFuture);
+        if (result != null && result == 0) {
+            return true;
+        } else {
+            unbindService();
+            return false;
+        }
+    }
+
+    /**
+     * Add data to the BackupTransport binder service.
+     * @param data List of ForensicEvent.
+     * @return Whether the data is added to the binder service.
+     */
+    public boolean addData(List<ForensicEvent> data) {
+        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        try {
+            mService.addData(data, resultFuture);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote Exception", e);
+            return false;
+        }
+        Integer result = getFutureResult(resultFuture);
+        return result != null && result == 0;
+    }
+
+    /**
+     * Release the BackupTransport binder service.
+     */
+    public void release() {
+        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        try {
+            mService.release(resultFuture);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote Exception", e);
+        } finally {
+            unbindService();
+        }
+    }
+
+    private <T> T getFutureResult(AndroidFuture<T> future) {
+        try {
+            return future.get(FUTURE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException
+                 | CancellationException e) {
+            Slog.w(TAG, "Failed to get result from transport:", e);
+            return null;
+        }
+    }
+
+    private boolean bindService() {
+        mForensicBackupTransportConfig = mContext.getString(
+                com.android.internal.R.string.config_forensicBackupTransport);
+        if (TextUtils.isEmpty(mForensicBackupTransportConfig)) {
+            return false;
+        }
+
+        ComponentName serviceComponent =
+                ComponentName.unflattenFromString(mForensicBackupTransportConfig);
+        if (serviceComponent == null) {
+            return false;
+        }
+
+        Intent intent = new Intent().setComponent(serviceComponent);
+        boolean result = mContext.bindServiceAsUser(
+                intent, this, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+        if (!result) {
+            unbindService();
+        }
+        return result;
+    }
+
+    private void unbindService() {
+        mContext.unbindService(this);
+        mService = null;
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mService = IBackupTransport.Stub.asInterface(service);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        mService = null;
+    }
+}
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 07639d1..20c648e 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -63,6 +63,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final BackupTransportConnection mBackupTransportConnection;
     private final BinderService mBinderService;
 
     private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
@@ -77,6 +78,7 @@
         super(injector.getContext());
         mContext = injector.getContext();
         mHandler = new EventHandler(injector.getLooper(), this);
+        mBackupTransportConnection = injector.getBackupTransportConnection();
         mBinderService = new BinderService(this);
     }
 
@@ -221,6 +223,10 @@
     private void enable(IForensicServiceCommandCallback callback) throws RemoteException {
         switch (mState) {
             case STATE_VISIBLE:
+                if (!mBackupTransportConnection.initialize()) {
+                    callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
+                    break;
+                }
                 mState = STATE_ENABLED;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -236,6 +242,7 @@
     private void disable(IForensicServiceCommandCallback callback) throws RemoteException {
         switch (mState) {
             case STATE_ENABLED:
+                mBackupTransportConnection.release();
                 mState = STATE_VISIBLE;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -266,6 +273,8 @@
         Context getContext();
 
         Looper getLooper();
+
+        BackupTransportConnection getBackupTransportConnection();
     }
 
     private static final class InjectorImpl implements Injector {
@@ -289,6 +298,11 @@
             serviceThread.start();
             return serviceThread.getLooper();
         }
+
+        @Override
+        public BackupTransportConnection getBackupTransportConnection() {
+            return new BackupTransportConnection(mContext);
+        }
     }
 }
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e7735d8..54e4f8e 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -48,6 +48,9 @@
 import static android.util.MathUtils.constrain;
 import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
 
+import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines;
+import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY;
@@ -68,9 +71,6 @@
 import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
 
@@ -209,6 +209,8 @@
 import com.android.internal.os.LooperStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.ProcfsMemoryUtil;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.internal.os.SelectedProcessCpuThreadReader;
 import com.android.internal.os.StoragedUidIoStatsReader;
 import com.android.internal.util.CollectionUtils;
@@ -229,7 +231,6 @@
 import com.android.server.power.stats.KernelWakelockStats;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.stats.pull.netstats.NetworkStatsAccumulator;
 import com.android.server.stats.pull.netstats.NetworkStatsExt;
 import com.android.server.stats.pull.netstats.SubInfo;
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 1e82b89..baf84cf 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -40,11 +40,11 @@
 import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 5bc2c2d..1fba297 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -47,11 +47,11 @@
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.util.LogUtils;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index a81ad22..189b608 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -90,11 +90,11 @@
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index 31ee247..78ff432 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -36,11 +36,11 @@
 import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.Objects;
 import java.util.Set;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index ad5bc72..0b9b677 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -46,11 +46,11 @@
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 53b0751..448a7eb 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -29,11 +29,11 @@
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.os.Handler;
 import android.os.ParcelUuid;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index 7ab8e55..1945052 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -22,10 +22,10 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.Objects;
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index c1f5a27..a2c2dfc 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -478,10 +478,10 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logForIntentRedirect(
+                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
                                     "Creator URI Grant Caused Exception.", intent, creatorUid,
-                                    creatorPackage, filterCallingUid, callingPackage);
-                            // TODO b/368559093 - rethrow the securityException.
+                                    creatorPackage, filterCallingUid, callingPackage,
+                                    securityException);
                         }
                     }
                     if ((aInfo.applicationInfo.privateFlags
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5d3ae54..2c4179f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -54,6 +54,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
 import static android.content.pm.ActivityInfo.launchModeToString;
 import static android.os.Process.INVALID_UID;
+import static android.security.Flags.preventIntentRedirectAbortOrThrowException;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -100,9 +101,11 @@
 import android.app.ProfilerInfo;
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -188,6 +191,10 @@
     @Disabled
     static final long ASM_RESTRICTIONS = 230590090L;
 
+    @ChangeId
+    @Overridable
+    private static final long ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION = 29623414L;
+
     private final ActivityTaskManagerService mService;
     private final RootWindowContainer mRootWindowContainer;
     private final ActivityTaskSupervisor mSupervisor;
@@ -608,11 +615,10 @@
             // Check if the Intent was redirected
             if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
                     != 0) {
-                ActivityStarter.logForIntentRedirect(
+                ActivityStarter.logAndThrowExceptionForIntentRedirect(
                         "Unparceled intent does not have a creator token set.", intent,
-                        intentCreatorUid,
-                        intentCreatorPackage, resolvedCallingUid, resolvedCallingPackage);
-                // TODO b/368559093 - eventually ramp up to throw SecurityException
+                        intentCreatorUid, intentCreatorPackage, resolvedCallingUid,
+                        resolvedCallingPackage, null);
             }
             if (IntentCreatorToken.isValid(intent)) {
                 IntentCreatorToken creatorToken = (IntentCreatorToken) intent.getCreatorToken();
@@ -645,11 +651,10 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logForIntentRedirect(
+                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
                                     "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
                                     intentCreatorPackage, resolvedCallingUid,
-                                    resolvedCallingPackage);
-                            // TODO b/368559093 - rethrow the securityException.
+                                    resolvedCallingPackage, securityException);
                         }
                     }
                 } else {
@@ -670,11 +675,10 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logForIntentRedirect(
+                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
                                     "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
                                     intentCreatorPackage, resolvedCallingUid,
-                                    resolvedCallingPackage);
-                            // TODO b/368559093 - rethrow the securityException.
+                                    resolvedCallingPackage, securityException);
                         }
                     }
                 }
@@ -1045,7 +1049,7 @@
         int callingUid = request.callingUid;
         int intentCreatorUid = request.intentCreatorUid;
         String intentCreatorPackage = request.intentCreatorPackage;
-        String intentCallingPackage = request.callingPackage;
+        String callingPackage = request.callingPackage;
         String callingFeatureId = request.callingFeatureId;
         final int realCallingPid = request.realCallingPid;
         final int realCallingUid = request.realCallingUid;
@@ -1130,7 +1134,7 @@
                 // launched in the app flow to redirect to an activity picked by the user, where
                 // we want the final activity to consider it to have been launched by the
                 // previous app activity.
-                intentCallingPackage = sourceRecord.launchedFromPackage;
+                callingPackage = sourceRecord.launchedFromPackage;
                 callingFeatureId = sourceRecord.launchedFromFeatureId;
             }
         }
@@ -1152,7 +1156,7 @@
                 if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
                     err = packageArchiver
                             .requestUnarchiveOnActivityStart(
-                                    intent, intentCallingPackage, mRequest.userId, realCallingUid);
+                                    intent, callingPackage, mRequest.userId, realCallingUid);
                 }
             }
         }
@@ -1211,7 +1215,7 @@
         boolean abort;
         try {
             abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
-                    requestCode, callingPid, callingUid, intentCallingPackage, callingFeatureId,
+                    requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
                     request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
                     resultRootTask);
         } catch (SecurityException e) {
@@ -1239,7 +1243,7 @@
         abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                 callingPid, resolvedType, aInfo.applicationInfo);
         abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
-                intentCallingPackage);
+                callingPackage);
 
         if (intentCreatorUid != Request.DEFAULT_INTENT_CREATOR_UID) {
             try {
@@ -1247,36 +1251,29 @@
                         requestCode, 0, intentCreatorUid, intentCreatorPackage, "",
                         request.ignoreTargetSecurity, inTask != null, null, resultRecord,
                         resultRootTask)) {
-                    logForIntentRedirect("Creator checkStartAnyActivityPermission Caused abortion.",
+                    abort = logAndAbortForIntentRedirect(
+                            "Creator checkStartAnyActivityPermission Caused abortion.",
                             intent, intentCreatorUid, intentCreatorPackage, callingUid,
-                            intentCallingPackage);
-                    // TODO b/368559093 - set abort to true.
-                    // abort = true;
+                            callingPackage);
                 }
             } catch (SecurityException e) {
-                logForIntentRedirect("Creator checkStartAnyActivityPermission Caused Exception.",
-                        intent, intentCreatorUid, intentCreatorPackage, callingUid,
-                        intentCallingPackage);
-                // TODO b/368559093 - rethrow the exception.
-                //throw e;
+                logAndThrowExceptionForIntentRedirect(
+                        "Creator checkStartAnyActivityPermission Caused Exception.",
+                        intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage,
+                        e);
             }
-            if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid, 0,
-                    resolvedType, aInfo.applicationInfo)) {
-                logForIntentRedirect("Creator IntentFirewall.checkStartActivity Caused abortion.",
-                        intent, intentCreatorUid, intentCreatorPackage, callingUid,
-                        intentCallingPackage);
-                // TODO b/368559093 - set abort to true.
-                // abort = true;
+            if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid,
+                    0, resolvedType, aInfo.applicationInfo)) {
+                abort = logAndAbortForIntentRedirect(
+                        "Creator IntentFirewall.checkStartActivity Caused abortion.",
+                        intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
 
-            if (!mService.getPermissionPolicyInternal().checkStartActivity(intent, intentCreatorUid,
-                    intentCreatorPackage)) {
-                logForIntentRedirect(
+            if (!mService.getPermissionPolicyInternal().checkStartActivity(intent,
+                    intentCreatorUid, intentCreatorPackage)) {
+                abort = logAndAbortForIntentRedirect(
                         "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
-                        intent, intentCreatorUid, intentCreatorPackage, callingUid,
-                        intentCallingPackage);
-                // TODO b/368559093 - set abort to true.
-                // abort = true;
+                        intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
             intent.removeCreatorTokenInfo();
         }
@@ -1296,7 +1293,7 @@
                         balController.checkBackgroundActivityStart(
                             callingUid,
                             callingPid,
-                                intentCallingPackage,
+                            callingPackage,
                             realCallingUid,
                             realCallingPid,
                             callerApp,
@@ -1317,7 +1314,7 @@
         if (request.allowPendingRemoteAnimationRegistryLookup) {
             checkedOptions = mService.getActivityStartController()
                     .getPendingRemoteAnimationRegistry()
-                    .overrideOptionsIfNeeded(intentCallingPackage, checkedOptions);
+                    .overrideOptionsIfNeeded(callingPackage, checkedOptions);
         }
         if (mService.mController != null) {
             try {
@@ -1334,7 +1331,7 @@
         final TaskDisplayArea suggestedLaunchDisplayArea =
                 computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
         mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags,
-                intentCallingPackage,
+                callingPackage,
                 callingFeatureId);
         if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
                 callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
@@ -1372,7 +1369,7 @@
             if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                     aInfo.packageName, userId)) {
                 final IIntentSender target = mService.getIntentSenderLocked(
-                        ActivityManager.INTENT_SENDER_ACTIVITY, intentCallingPackage,
+                        ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
                         callingFeatureId,
                         callingUid, userId, null, null, 0, new Intent[]{intent},
                         new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
@@ -1436,7 +1433,7 @@
         // app [on install success].
         if (rInfo != null && rInfo.auxiliaryInfo != null) {
             intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent,
-                    intentCallingPackage, callingFeatureId, verificationBundle, resolvedType,
+                    callingPackage, callingFeatureId, verificationBundle, resolvedType,
                     userId);
             resolvedType = null;
             callingUid = realCallingUid;
@@ -1460,7 +1457,7 @@
                 .setCaller(callerApp)
                 .setLaunchedFromPid(callingPid)
                 .setLaunchedFromUid(callingUid)
-                .setLaunchedFromPackage(intentCallingPackage)
+                .setLaunchedFromPackage(callingPackage)
                 .setLaunchedFromFeature(callingFeatureId)
                 .setIntent(intent)
                 .setResolvedType(resolvedType)
@@ -3588,16 +3585,32 @@
         pw.println(mInTaskFragment);
     }
 
-    static void logForIntentRedirect(String message, Intent intent, int intentCreatorUid,
-            String intentCreatorPackage, int callingUid, String callingPackage) {
+    static void logAndThrowExceptionForIntentRedirect(@NonNull String message,
+            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+            int callingUid, @Nullable String callingPackage,
+            @Nullable SecurityException originalException) {
         String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
                 intentCreatorPackage, callingUid, callingPackage);
         Slog.wtf(TAG, msg);
+        if (preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
+                ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid)) {
+            throw new SecurityException(msg, originalException);
+        }
     }
 
-    private static String getIntentRedirectPreventedLogMessage(String message, Intent intent,
-            int intentCreatorUid, String intentCreatorPackage, int callingUid,
-            String callingPackage) {
+    private static boolean logAndAbortForIntentRedirect(@NonNull String message,
+            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+            int callingUid, @Nullable String callingPackage) {
+        String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+                intentCreatorPackage, callingUid, callingPackage);
+        Slog.wtf(TAG, msg);
+        return preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
+                ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid);
+    }
+
+    private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
+            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+            int callingUid, @Nullable String callingPackage) {
         return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
                 + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
                 + "; callingPackage: " + callingPackage + "; intent: " + intent;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 4f71719..567eca2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2572,14 +2572,21 @@
         wpc.computeProcessActivityState();
     }
 
-    void computeProcessActivityStateBatch() {
+    boolean computeProcessActivityStateBatch() {
         if (mActivityStateChangedProcs.isEmpty()) {
-            return;
+            return false;
         }
+        boolean changed = false;
         for (int i = mActivityStateChangedProcs.size() - 1; i >= 0; i--) {
-            mActivityStateChangedProcs.get(i).computeProcessActivityState();
+            final WindowProcessController wpc = mActivityStateChangedProcs.get(i);
+            final int prevState = wpc.getActivityStateFlags();
+            wpc.computeProcessActivityState();
+            if (!changed && prevState != wpc.getActivityStateFlags()) {
+                changed = true;
+            }
         }
         mActivityStateChangedProcs.clear();
+        return changed;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index ccd5996..6cc4b1e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -994,7 +994,9 @@
         // Ensure the final animation targets which hidden by transition could be visible.
         for (int i = 0; i < targets.size(); i++) {
             final WindowContainer wc = targets.get(i).mContainer;
-            wc.prepareSurfaces();
+            if (wc.mSurfaceControl != null) {
+                wc.prepareSurfaces();
+            }
         }
 
         // The pending builder could be cleared due to prepareSurfaces
@@ -1328,16 +1330,13 @@
             if (!allWindowDrawn) {
                 return;
             }
-            final SurfaceControl startingSurface = mOpenAnimAdaptor.mStartingSurface;
-            if (startingSurface != null && startingSurface.isValid()) {
-                startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
-                    synchronized (mWindowManagerService.mGlobalLock) {
-                        if (mOpenAnimAdaptor != null) {
-                            mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
-                        }
+            startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
+                synchronized (mWindowManagerService.mGlobalLock) {
+                    if (mOpenAnimAdaptor != null) {
+                        mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
                     }
-                });
-            }
+                }
+            });
         }
 
         void clearBackAnimateTarget(boolean cancel) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 41a5804..6707a27 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -105,6 +105,7 @@
 import android.content.pm.UserProperties;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.power.Mode;
@@ -153,6 +154,7 @@
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.utils.Slogf;
+import com.android.server.wm.utils.RegionUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -270,6 +272,8 @@
     private boolean mTaskLayersChanged = true;
     private int mTmpTaskLayerRank;
     private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
+    private Region mTmpOccludingRegion;
+    private Region mTmpTaskRegion;
 
     private String mDestroyAllActivitiesReason;
     private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@@ -2921,6 +2925,11 @@
         });
     }
 
+    void invalidateTaskLayersAndUpdateOomAdjIfNeeded() {
+        mRankTaskLayersRunnable.mCheckUpdateOomAdj = true;
+        invalidateTaskLayers();
+    }
+
     void invalidateTaskLayers() {
         if (!mTaskLayersChanged) {
             mTaskLayersChanged = true;
@@ -2938,13 +2947,18 @@
         // Only rank for leaf tasks because the score of activity is based on immediate parent.
         forAllLeafTasks(task -> {
             final int oldRank = task.mLayerRank;
-            final ActivityRecord r = task.topRunningActivityLocked();
-            if (r != null && r.isVisibleRequested()) {
+            final int oldRatio = task.mNonOccludedFreeformAreaRatio;
+            task.mNonOccludedFreeformAreaRatio = 0;
+            if (task.isVisibleRequested()) {
                 task.mLayerRank = ++mTmpTaskLayerRank;
+                if (task.inFreeformWindowingMode()) {
+                    computeNonOccludedFreeformAreaRatio(task);
+                }
             } else {
                 task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
             }
-            if (task.mLayerRank != oldRank) {
+            if (task.mLayerRank != oldRank
+                    || task.mNonOccludedFreeformAreaRatio != oldRatio) {
                 task.forAllActivities(activity -> {
                     if (activity.hasProcess()) {
                         mTaskSupervisor.onProcessActivityStateChanged(activity.app,
@@ -2953,10 +2967,40 @@
                 });
             }
         }, true /* traverseTopToBottom */);
-
-        if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
-            mTaskSupervisor.computeProcessActivityStateBatch();
+        if (mTmpOccludingRegion != null) {
+            mTmpOccludingRegion.setEmpty();
         }
+        boolean changed = false;
+        if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
+            changed = mTaskSupervisor.computeProcessActivityStateBatch();
+        }
+        if (mRankTaskLayersRunnable.mCheckUpdateOomAdj) {
+            mRankTaskLayersRunnable.mCheckUpdateOomAdj = false;
+            if (changed) {
+                mService.updateOomAdj();
+            }
+        }
+    }
+
+    /** This method is called for visible freeform task from top to bottom. */
+    private void computeNonOccludedFreeformAreaRatio(@NonNull Task task) {
+        if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+            return;
+        }
+        if (mTmpOccludingRegion == null) {
+            mTmpOccludingRegion = new Region();
+            mTmpTaskRegion = new Region();
+        }
+        final Rect taskBounds = task.getBounds();
+        mTmpTaskRegion.set(taskBounds);
+        // Exclude the area outside the display.
+        mTmpTaskRegion.op(task.mDisplayContent.getBounds(), Region.Op.INTERSECT);
+        // Exclude the area covered by the above tasks.
+        mTmpTaskRegion.op(mTmpOccludingRegion, Region.Op.DIFFERENCE);
+        task.mNonOccludedFreeformAreaRatio = 100 * RegionUtils.getAreaSize(mTmpTaskRegion)
+                        / (taskBounds.width() * taskBounds.height());
+        // Accumulate the occluding region for other visible tasks behind.
+        mTmpOccludingRegion.op(taskBounds, Region.Op.UNION);
     }
 
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
@@ -3862,6 +3906,8 @@
     }
 
     private class RankTaskLayersRunnable implements Runnable {
+        boolean mCheckUpdateOomAdj;
+
         @Override
         public void run() {
             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4861341..8a624b3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -432,6 +432,9 @@
     // This number will be assigned when we evaluate OOM scores for all visible tasks.
     int mLayerRank = LAYER_RANK_INVISIBLE;
 
+    /** A 0~100 ratio to indicate the percentage of visible area on screen of a freeform task. */
+    int mNonOccludedFreeformAreaRatio;
+
     /* Unique identifier for this task. */
     final int mTaskId;
     /* User for which this task was created. */
@@ -1207,6 +1210,28 @@
     }
 
     @Override
+    void onResize() {
+        super.onResize();
+        updateTaskLayerForFreeform();
+    }
+
+    @Override
+    void onMovedByResize() {
+        super.onMovedByResize();
+        updateTaskLayerForFreeform();
+    }
+
+    private void updateTaskLayerForFreeform() {
+        if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+            return;
+        }
+        if (!isVisibleRequested() || !inFreeformWindowingMode()) {
+            return;
+        }
+        mRootWindowContainer.invalidateTaskLayersAndUpdateOomAdjIfNeeded();
+    }
+
+    @Override
     @Nullable
     ActivityRecord getTopResumedActivity() {
         if (!isLeafTask()) {
@@ -3410,6 +3435,36 @@
         info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
                 ? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
         AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
+        info.topActivityMainWindowFrame = calculateTopActivityMainWindowFrameForTaskInfo(top);
+    }
+
+    /**
+     * Returns the top activity's main window frame if it doesn't match
+     * {@link ActivityRecord#getBounds() the top activity bounds}, or {@code null}, otherwise.
+     *
+     * @param top The top running activity of the task
+     */
+    @Nullable
+    private static Rect calculateTopActivityMainWindowFrameForTaskInfo(
+            @Nullable ActivityRecord top) {
+        if (!Flags.betterSupportNonMatchParentActivity()) {
+            return null;
+        }
+        if (top == null) {
+            return null;
+        }
+        final WindowState mainWindow = top.findMainWindow();
+        if (mainWindow == null) {
+            return null;
+        }
+        if (!mainWindow.mHaveFrame) {
+            return null;
+        }
+        final Rect windowFrame = mainWindow.getFrame();
+        if (top.getBounds().equals(windowFrame)) {
+            return null;
+        }
+        return windowFrame;
     }
 
     /**
@@ -5919,6 +5974,10 @@
             pw.print(prefix); pw.print("  mLastNonFullscreenBounds=");
             pw.println(mLastNonFullscreenBounds);
         }
+        if (mNonOccludedFreeformAreaRatio != 0) {
+            pw.print(prefix); pw.print("  mNonOccludedFreeformAreaRatio=");
+            pw.println(mNonOccludedFreeformAreaRatio);
+        }
         if (isLeafTask()) {
             pw.println(prefix + "  isSleeping=" + shouldSleepActivities());
             printThisActivity(pw, getTopPausingActivity(), dumpPackage, false,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3ffeacf..d6ba312 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -3027,7 +3027,11 @@
         // The task order may be changed by finishIfPossible() for adjusting focus if there are
         // nested tasks, so add all activities into a list to avoid missed removals.
         final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
-        forAllActivities((Consumer<ActivityRecord>) removingActivities::add);
+        forAllActivities((r) -> {
+            if (!r.finishing) {
+                removingActivities.add(r);
+            }
+        });
         for (int i = removingActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = removingActivities.get(i);
             if (withTransition && r.isVisible()) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1a107c2..7f0c336 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -121,6 +121,11 @@
      */
     private static final int MAX_NUM_PERCEPTIBLE_FREEFORM =
             SystemProperties.getInt("persist.wm.max_num_perceptible_freeform", 1);
+    /**
+     * If the visible area percentage of a resumed freeform task is greater than or equal to this
+     * ratio, its process will have a higher priority.
+     */
+    private static final int PERCEPTIBLE_FREEFORM_VISIBLE_RATIO = 90;
 
     private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200;
     private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
@@ -1234,6 +1239,7 @@
         boolean hasResumedFreeform = false;
         int minTaskLayer = Integer.MAX_VALUE;
         int stateFlags = 0;
+        int nonOccludedRatio = 0;
         final boolean wasResumed = hasResumedActivity();
         final boolean wasAnyVisible = (mActivityStateFlags
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
@@ -1261,6 +1267,8 @@
                         stateFlags |= ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN;
                     } else if (windowingMode == WINDOWING_MODE_FREEFORM) {
                         hasResumedFreeform = true;
+                        nonOccludedRatio =
+                                Math.max(task.mNonOccludedFreeformAreaRatio, nonOccludedRatio);
                     }
                 }
                 if (minTaskLayer > 0) {
@@ -1298,7 +1306,8 @@
                 && com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
                 // Exclude task layer 1 because it is already the top most.
                 && minTaskLayer > 1) {
-            if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+            if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM
+                    || nonOccludedRatio >= PERCEPTIBLE_FREEFORM_VISIBLE_RATIO) {
                 stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
             } else {
                 stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index ce7776f..ff23145 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -81,4 +81,15 @@
         Collections.reverse(rects);
         rects.forEach(rectConsumer);
     }
+
+    /** Returns the area size of the region. */
+    public static int getAreaSize(Region region) {
+        final RegionIterator regionIterator = new RegionIterator(region);
+        int area = 0;
+        final Rect rect = new Rect();
+        while (regionIterator.next(rect)) {
+            area += rect.width() * rect.height();
+        }
+        return area;
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0b7ce75..2461c1c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -106,6 +106,7 @@
 import com.android.internal.os.ApplicationSharedMemory;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
+import com.android.internal.pm.RoSystemFeatures;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.protolog.ProtoLogConfigurationServiceImpl;
@@ -1495,8 +1496,7 @@
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
                 false);
 
-        boolean isWatch = context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_WATCH);
+        boolean isWatch = RoSystemFeatures.hasFeatureWatch(context);
 
         boolean isArc = context.getPackageManager().hasSystemFeature(
                 "org.chromium.arc");
@@ -1504,6 +1504,8 @@
         boolean isTv = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
 
+        boolean isAutomotive = RoSystemFeatures.hasFeatureAutomotive(context);
+
         boolean enableVrService = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
 
@@ -1760,7 +1762,8 @@
                 t.traceEnd();
             }
 
-            if (android.security.Flags.aapmApi()) {
+            if (!isWatch && !isTv && !isAutomotive
+                    && android.security.Flags.aapmApi()) {
                 t.traceBegin("StartAdvancedProtectionService");
                 mSystemServiceManager.startService(AdvancedProtectionService.Lifecycle.class);
                 t.traceEnd();
@@ -2758,7 +2761,7 @@
             t.traceEnd();
         }
 
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+        if (RoSystemFeatures.hasFeatureEmbedded(context)) {
             t.traceBegin("StartIoTSystemService");
             mSystemServiceManager.startService(IOT_SERVICE_CLASS);
             t.traceEnd();
@@ -3137,8 +3140,6 @@
                 }, WEBVIEW_PREPARATION);
             }
 
-            boolean isAutomotive = mPackageManager
-                    .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
             if (isAutomotive) {
                 t.traceBegin("StartCarServiceHelperService");
                 final SystemService cshs = mSystemServiceManager
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index f15e533..2f00a1b 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -32,6 +32,7 @@
         "androidx.test.runner",
         "truth",
         "Harrier",
+        "bedstead-multiuser",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index 70a2d48..48cebd7 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.MOVE_PACKAGE;
 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
 
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -112,9 +113,9 @@
         final UserReference primaryUser = sDeviceState.primaryUser();
         if (primaryUser.id() == UserHandle.myUserId()) {
             mCurrentUser = primaryUser;
-            mOtherUser = sDeviceState.secondaryUser();
+            mOtherUser = secondaryUser(sDeviceState);
         } else {
-            mCurrentUser = sDeviceState.secondaryUser();
+            mCurrentUser = secondaryUser(sDeviceState);
             mOtherUser = primaryUser;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index fdf6b80..9189c2f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2224,6 +2224,8 @@
         verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
                 /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
                 eq(false));
+        // This brightness shouldn't be stored in the setting
+        verify(mHolder.brightnessSetting, never()).setBrightness(DEFAULT_DOZE_BRIGHTNESS);
 
         // The display device changes and the default doze brightness changes
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 5c718d9..b2fe138 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1726,6 +1726,8 @@
         }
 
         // Top-started job
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+        // Top-stared jobs are out of quota enforcement.
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
             trackJobs(job, jobDefIWF, jobHigh);
@@ -1755,6 +1757,38 @@
             assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
         }
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+        // Quota is enforced for top-started job after the process leaves TOP/BTOP state.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            trackJobs(job, jobDefIWF, jobHigh);
+            mQuotaController.prepareForExecutionLocked(job);
+            mQuotaController.prepareForExecutionLocked(jobDefIWF);
+            mQuotaController.prepareForExecutionLocked(jobHigh);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
+        }
+
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
+        }
     }
 
     @Test
@@ -1824,6 +1858,7 @@
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
 
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
@@ -1831,6 +1866,7 @@
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
+            // Top-started job is out of quota enforcement.
             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1842,6 +1878,28 @@
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
 
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+        // Top-started job
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        synchronized (mQuotaController.mLock) {
+            // Top-started job is enforced by quota policy after the app leaves the TOP state.
+            // The max execution time should be the total EJ session limit of the RARE bucket
+            // minus the time has been used.
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
+        }
+
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+        }
+
         // Test used quota rolling out of window.
         synchronized (mQuotaController.mLock) {
             mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
@@ -1856,6 +1914,7 @@
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
 
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
@@ -1864,6 +1923,7 @@
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
+            // Top-started job is out of quota enforcement.
             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1874,6 +1934,28 @@
             assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+        // Top-started job
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        synchronized (mQuotaController.mLock) {
+            // Top-started job is enforced by quota policy after the app leaves the TOP state.
+            // The max execution time should be the total EJ session limit of the RARE bucket.
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
+        }
+
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+        }
     }
 
     @Test
@@ -1902,6 +1984,7 @@
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
 
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
@@ -1909,6 +1992,7 @@
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
+            // Top-started job is out of quota enforcement.
             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1920,6 +2004,27 @@
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
 
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+        // Top-started job
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        synchronized (mQuotaController.mLock) {
+            // Top-started job is enforced by quota policy after the app leaves the TOP state.
+            // The max execution time should be the total EJ session limit of the RARE bucket.
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
+        }
+
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+        }
+
         // Test used quota rolling out of window.
         synchronized (mQuotaController.mLock) {
             mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
@@ -1935,6 +2040,7 @@
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
 
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
@@ -1943,6 +2049,7 @@
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
+            // Top-started job is out of quota enforcement.
             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1953,6 +2060,28 @@
             assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
         }
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+        // Top-started job
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        synchronized (mQuotaController.mLock) {
+            // Top-started job is enforced by quota policy after the app leaves the TOP state.
+            // The max execution time should be the total EJ session limit of the RARE bucket.
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
+        }
+
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+        }
     }
 
     /**
@@ -4608,6 +4737,7 @@
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
         advanceElapsedClock(SECOND_IN_MILLIS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
 
         // Bg job starts while inactive, spans an entire active session, and ends after the
         // active session.
@@ -4686,8 +4816,66 @@
             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
             mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
         }
+        // jobBg2 and jobFg1 are counted, jobTop is not counted.
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+
+        // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+        // foreground_service and a new job starts. Shortly after, uid goes
+        // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+        // This should result in two TimingSessions:
+        //  * The first should have a count of 1
+        //  * The second should have a count of 2, which accounts for the bg2 and fg and top jobs.
+        //    Top started jobs are not quota free any more if the process leaves TOP/BTOP state.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+            mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+            mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+            mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobBg1);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobTop);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
+        }
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        setProcessState(getProcessStateQuotaFreeThreshold());
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobFg1);
+        }
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobBg2);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
+        }
+        // jobBg2, jobFg1 and jobTop are counted.
+        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 3));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
 
     /**
@@ -4807,7 +4995,8 @@
      * Tests that TOP jobs aren't stopped when an app runs out of quota.
      */
     @Test
-    public void testTracking_OutOfQuota_ForegroundAndBackground() {
+    @DisableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+    public void testTracking_OutOfQuota_ForegroundAndBackground_DisableTopStartedJobsThrottling() {
         setDischarging();
 
         JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
@@ -4851,6 +5040,7 @@
         // Go to a background state.
         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         advanceElapsedClock(remainingTimeMs / 2 + 1);
+        // Only Bg job will be changed from in-quota to out-of-quota.
         inOrder.verify(mJobSchedulerService,
                         timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
@@ -4897,6 +5087,105 @@
     }
 
     /**
+     * Tests that TOP jobs are stopped when an app runs out of quota.
+     */
+    @Test
+    @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+    public void testTracking_OutOfQuota_ForegroundAndBackground_EnableTopStartedJobsThrottling() {
+        setDischarging();
+
+        JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+        JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+        trackJobs(jobBg, jobTop);
+        setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+        // Now the package only has 20 seconds to run.
+        final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(
+                        JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+                        10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
+
+        InOrder inOrder = inOrder(mJobSchedulerService);
+
+        // UID starts out inactive.
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        // Start the job.
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobBg);
+        }
+        advanceElapsedClock(remainingTimeMs / 2);
+        // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+        // should continue to have remainingTimeMs / 2 time remaining.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobTop);
+        }
+        advanceElapsedClock(remainingTimeMs);
+
+        // Wait for some extra time to allow for job processing.
+        inOrder.verify(mJobSchedulerService,
+                        timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+        synchronized (mQuotaController.mLock) {
+            assertEquals(remainingTimeMs / 2,
+                    mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+            assertEquals(remainingTimeMs / 2,
+                    mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+        }
+        // Go to a background state.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        advanceElapsedClock(remainingTimeMs / 2 + 1);
+        // Both Bg and Top jobs should be changed from in-quota to out-of-quota
+        inOrder.verify(mJobSchedulerService,
+                        timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+        // Top job should NOT be allowed to run.
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertFalse(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+        // New jobs to run.
+        JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+        JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+        JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+        setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        // Both Bg and Top jobs should be changed from out-of-quota to in-quota.
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+        trackJobs(jobFg, jobTop);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobTop);
+        }
+        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+        // App still in foreground so everything should be in quota.
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(getProcessStateQuotaFreeThreshold());
+        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        // App is in background so everything should be out of quota.
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        // Bg, Fg and Top jobs should be changed from in-quota to out-of-quota.
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+        // App is now in background and out of quota. Fg should now change to out of quota
+        // since it wasn't started. Top should now changed to out of quota even it started
+        // when the app was in TOP.
+        assertFalse(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        trackJobs(jobBg2);
+        assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+    }
+
+    /**
      * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
      * its quota.
      */
@@ -6280,6 +6569,7 @@
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
         advanceElapsedClock(SECOND_IN_MILLIS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
 
         // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
         // foreground_service and a new job starts. Shortly after, uid goes
@@ -6333,6 +6623,63 @@
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+
+        // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+        // foreground_service and a new job starts. Shortly after, uid goes
+        // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+        // This should result in two TimingSessions:
+        //  * The first should have a count of 1
+        //  * The second should have a count of 3, which accounts for the bg2, fg and top jobs.
+        //    Top started jobs are not quota free any more if the process leaves TOP/BTOP state.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+            mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+            mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+            mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+        }
+        setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobBg1);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobTop);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
+        }
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        setProcessState(getProcessStateQuotaFreeThreshold());
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobFg1);
+        }
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobBg2);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
+        }
+        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 3));
+        assertEquals(expected,
+                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
 
     /**
@@ -6701,7 +7048,8 @@
      * Tests that expedited jobs aren't stopped when an app runs out of quota.
      */
     @Test
-    public void testEJTracking_OutOfQuota_ForegroundAndBackground() {
+    @DisableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+    public void testEJTracking_OutOfQuota_ForegroundAndBackground_DisableTopStartedJobsThrottling() {
         setDischarging();
         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
 
@@ -6813,6 +7161,129 @@
     }
 
     /**
+     * Tests that expedited jobs are stopped when an app runs out of quota.
+     */
+    @Test
+    @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+    public void testEJTracking_OutOfQuota_ForegroundAndBackground_EnableTopStartedJobsThrottling() {
+        setDischarging();
+        setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
+
+        JobStatus jobBg =
+                createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 1);
+        JobStatus jobTop =
+                createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 2);
+        JobStatus jobUnstarted =
+                createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 3);
+        trackJobs(jobBg, jobTop, jobUnstarted);
+        setStandbyBucket(WORKING_INDEX, jobTop, jobBg, jobUnstarted);
+        // Now the package only has 20 seconds to run.
+        final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(
+                        JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+                        mQcConstants.EJ_LIMIT_WORKING_MS - remainingTimeMs, 1), true);
+
+        InOrder inOrder = inOrder(mJobSchedulerService);
+
+        // UID starts out inactive.
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        // Start the job.
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobBg);
+        }
+        advanceElapsedClock(remainingTimeMs / 2);
+        // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+        // should continue to have remainingTimeMs / 2 time remaining.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobTop);
+        }
+        advanceElapsedClock(remainingTimeMs);
+
+        // Wait for some extra time to allow for job processing.
+        inOrder.verify(mJobSchedulerService,
+                        timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+        synchronized (mQuotaController.mLock) {
+            assertEquals(remainingTimeMs / 2,
+                    mQuotaController.getRemainingEJExecutionTimeLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE));
+        }
+        // Go to a background state.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        advanceElapsedClock(remainingTimeMs / 2 + 1);
+        // Bg, Top and jobUnstarted should be changed from in-quota to out-of-quota.
+        inOrder.verify(mJobSchedulerService,
+                        timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+        // Top should still NOT be "in quota" even it started before the app
+        // ran on top out of quota.
+        assertFalse(jobBg.isExpeditedQuotaApproved());
+        assertFalse(jobTop.isExpeditedQuotaApproved());
+        assertFalse(jobUnstarted.isExpeditedQuotaApproved());
+        synchronized (mQuotaController.mLock) {
+            assertTrue(
+                    0 >= mQuotaController
+                            .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
+        }
+
+        // New jobs to run.
+        JobStatus jobBg2 =
+                createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 4);
+        JobStatus jobTop2 =
+                createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 5);
+        JobStatus jobFg =
+                createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 6);
+        setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
+        // jobBg, jobFg and jobUnstarted are changed from out-of-quota to in-quota.
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+        trackJobs(jobTop2, jobFg);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobTop2);
+        }
+        assertTrue(jobTop.isExpeditedQuotaApproved());
+        assertTrue(jobTop2.isExpeditedQuotaApproved());
+        assertTrue(jobFg.isExpeditedQuotaApproved());
+        assertTrue(jobBg.isExpeditedQuotaApproved());
+        assertTrue(jobUnstarted.isExpeditedQuotaApproved());
+
+        // App still in foreground so everything should be in quota.
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(getProcessStateQuotaFreeThreshold());
+        assertTrue(jobTop.isExpeditedQuotaApproved());
+        assertTrue(jobTop2.isExpeditedQuotaApproved());
+        assertTrue(jobFg.isExpeditedQuotaApproved());
+        assertTrue(jobBg.isExpeditedQuotaApproved());
+        assertTrue(jobUnstarted.isExpeditedQuotaApproved());
+
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        // Bg, Fg, Top, Top2 and jobUnstarted should be changed from in-quota to out-of-quota
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 5));
+        // App is now in background and out of quota. Fg should now change to out of quota since it
+        // wasn't started. Top should change to out of quota as the app leaves TOP state.
+        assertFalse(jobTop.isExpeditedQuotaApproved());
+        assertFalse(jobTop2.isExpeditedQuotaApproved());
+        assertFalse(jobFg.isExpeditedQuotaApproved());
+        assertFalse(jobBg.isExpeditedQuotaApproved());
+        trackJobs(jobBg2);
+        assertFalse(jobBg2.isExpeditedQuotaApproved());
+        assertFalse(jobUnstarted.isExpeditedQuotaApproved());
+        synchronized (mQuotaController.mLock) {
+            assertTrue(
+                    0 >= mQuotaController
+                            .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
+        }
+    }
+
+    /**
      * Tests that Timers properly track overlapping top and background jobs.
      */
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index 625dbe6..bf946a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -41,14 +41,19 @@
 import android.media.AudioPlaybackConfiguration;
 import android.media.PlayerProxy;
 import android.media.audiopolicy.AudioPolicy;
+import android.multiuser.Flags;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -60,7 +65,6 @@
 import java.util.Stack;
 
 @RunWith(JUnit4.class)
-
 public class BackgroundUserSoundNotifierTest {
     private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
             .getTargetContext();
@@ -72,6 +76,10 @@
 
     @Mock
     private NotificationManager mNotificationManager;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -142,8 +150,11 @@
         final int fgUserId = mSpiedContext.getUserId();
         int bgUserId = fgUserId + 1;
         int bgUserUid = bgUserId * 100000;
-        mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
-
+        if (Flags.multipleAlarmNotificationsSupport()) {
+            mBackgroundUserSoundNotifier.mNotificationClientUids.add(bgUserUid);
+        } else {
+            mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
+        }
         AudioManager mockAudioManager = mock(AudioManager.class);
         when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);
 
@@ -243,6 +254,72 @@
                 notification.actions[0].title);
     }
 
+    @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
+    @Test
+    public void testMultipleAlarmsSameUid_OneNotificationCreated() throws RemoteException {
+        assumeTrue(UserManager.supportsMultipleUsers());
+        UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+        final int fgUserId = mSpiedContext.getUserId();
+        final int bgUserUid = user.id * 100000;
+        doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
+
+        AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+        AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid, "",
+                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+
+        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
+        verify(mNotificationManager)
+                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+                        eq(afi1.getClientUid()), any(Notification.class),
+                        eq(UserHandle.of(fgUserId)));
+
+        AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid, "",
+                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+        clearInvocations(mNotificationManager);
+        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
+        verify(mNotificationManager, never())
+                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+                        eq(afi2.getClientUid()), any(Notification.class),
+                        eq(UserHandle.of(fgUserId)));
+    }
+
+    @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
+    @Test
+    public void testMultipleAlarmsDifferentUsers_multipleNotificationsCreated()
+            throws RemoteException {
+        assumeTrue(UserManager.supportsMultipleUsers());
+        UserInfo user1 = createUser("User1", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+        UserInfo user2 = createUser("User2", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+        final int fgUserId = mSpiedContext.getUserId();
+        final int bgUserUid1 = user1.id * 100000;
+        final int bgUserUid2 = user2.id * 100000;
+        doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
+
+        AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+        AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid1, "",
+                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+
+        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
+        verify(mNotificationManager)
+                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+                        eq(afi1.getClientUid()), any(Notification.class),
+                        eq(UserHandle.of(fgUserId)));
+
+        AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid2, "",
+                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+        clearInvocations(mNotificationManager);
+        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
+        verify(mNotificationManager)
+                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+                        eq(afi2.getClientUid()), any(Notification.class),
+                        eq(UserHandle.of(fgUserId)));
+    }
+
+
     private UserInfo createUser(String name, String userType, int flags) {
         UserInfo user = mUserManager.createUser(name, userType, flags);
         if (user != null) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 177f30a..0a1fc31 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -987,5 +987,7 @@
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
                 .isEqualTo(60000);
+
+        span.close();
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
index 9a64ce1..94997b2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -41,7 +41,7 @@
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
-    public void testBatteryUsageStatsDataConsistency() {
+    public void testBatteryUsageStatsDataConsistency() throws Exception {
         BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
         BatteryUsageStats stats = bsm.getBatteryUsageStats(
                 new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
@@ -70,5 +70,7 @@
                 }
             }
         }
+
+        stats.close();
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 7d2bc17..813dd84 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -64,7 +64,7 @@
     private static final int UID_3 = 4000;
 
     @Test
-    public void testAtom_BatteryUsageStatsPerUid() {
+    public void testAtom_BatteryUsageStatsPerUid() throws Exception {
         final BatteryUsageStats bus = buildBatteryUsageStats();
         BatteryStatsService.FrameworkStatsLogger statsLogger =
                 mock(BatteryStatsService.FrameworkStatsLogger.class);
@@ -72,6 +72,8 @@
         List<StatsEvent> actual = new ArrayList<>();
         new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual);
 
+        bus.close();
+
         if (DEBUG) {
             System.out.println(mockingDetails(statsLogger).printInvocations());
         }
@@ -284,7 +286,7 @@
     }
 
     @Test
-    public void testAtom_BatteryUsageStatsAtomsProto() {
+    public void testAtom_BatteryUsageStatsAtomsProto() throws Exception {
         final BatteryUsageStats bus = buildBatteryUsageStats();
         final byte[] bytes = bus.getStatsProto();
         BatteryUsageStatsAtomsProto proto;
@@ -347,6 +349,7 @@
 
         // UID_3 - Should be none, since no interesting data (done last for debugging convenience).
         assertEquals(3, proto.uidBatteryConsumers.length);
+        bus.close();
     }
 
     private void assertSameBatteryConsumer(String message, BatteryConsumer consumer,
@@ -582,7 +585,7 @@
     }
 
     @Test
-    public void testLargeAtomTruncated() {
+    public void testLargeAtomTruncated() throws Exception {
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
         // If not truncated, this BatteryUsageStats object would generate a proto buffer
@@ -618,6 +621,8 @@
         assertThat(bytes.length).isGreaterThan(20000);
         assertThat(bytes.length).isLessThan(50000);
 
+        batteryUsageStats.close();
+
         BatteryUsageStatsAtomsProto proto;
         try {
             proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index e9d95fc..87a26d0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -94,7 +94,7 @@
     }
 
     @Test
-    public void test_getBatteryUsageStats() {
+    public void test_getBatteryUsageStats() throws IOException {
         final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
 
         final List<UidBatteryConsumer> uidBatteryConsumers =
@@ -121,24 +121,27 @@
 
         assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
         assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(180 * MINUTE_IN_MS);
+        batteryUsageStats.close();
     }
 
     @Test
-    public void batteryLevelInfo_charging() {
+    public void batteryLevelInfo_charging() throws IOException {
         final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(true);
         assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
         assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(1_200_000);
+        batteryUsageStats.close();
     }
 
     @Test
-    public void batteryLevelInfo_onBattery() {
+    public void batteryLevelInfo_onBattery() throws IOException {
         final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
         assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
         assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(600_000);
+        batteryUsageStats.close();
     }
 
     @Test
-    public void test_selectPowerComponents() {
+    public void test_selectPowerComponents() throws IOException {
         BatteryStatsImpl batteryStats = prepareBatteryStats(false);
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
@@ -163,6 +166,8 @@
         assertThat(
                 uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
                 .isEqualTo(0);
+
+        batteryUsageStats.close();
     }
 
     private BatteryStatsImpl prepareBatteryStats(boolean plugInAtTheEnd) {
@@ -274,7 +279,7 @@
     }
 
     @Test
-    public void testWriteAndReadHistory() {
+    public void testWriteAndReadHistory() throws IOException {
         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
         synchronized (batteryStats) {
             batteryStats.setRecordAllHistoryLocked(true);
@@ -306,6 +311,8 @@
 
         Parcel in = Parcel.obtain();
         batteryUsageStats.writeToParcel(in, 0);
+        batteryUsageStats.close();
+
         final byte[] bytes = in.marshall();
 
         Parcel out = Parcel.obtain();
@@ -349,10 +356,12 @@
 
         assertThat(iterator.hasNext()).isFalse();
         assertThat(iterator.next()).isNull();
+
+        unparceled.close();
     }
 
     @Test
-    public void testWriteAndReadHistoryTags() {
+    public void testWriteAndReadHistoryTags() throws IOException {
         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
         synchronized (batteryStats) {
             batteryStats.setRecordAllHistoryLocked(true);
@@ -400,6 +409,8 @@
             assertThat(parcel.dataSize()).isAtMost(128_000);
         }
 
+        batteryUsageStats.close();
+
         parcel.setDataPosition(0);
 
         BatteryUsageStats unparceled = parcel.readParcelable(getClass().getClassLoader(),
@@ -461,7 +472,7 @@
     }
 
     @Test
-    public void testAggregateBatteryStats() {
+    public void testAggregateBatteryStats() throws IOException {
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
         setTime(5 * MINUTE_IN_MS);
@@ -563,6 +574,8 @@
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
                 .isWithin(0.1)
                 .of(180.0);
+
+        stats.close();
     }
 
     @Test
@@ -689,6 +702,8 @@
                 .isEqualTo(60 * MINUTE_IN_MS);
 
         assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
+
+        stats.close();
     }
 
     private void setTime(long timeMs) {
@@ -733,6 +748,7 @@
                     .isWithin(PRECISION).of(8.33333);
             assertThat(uid.getConsumedPower(componentId1))
                     .isWithin(PRECISION).of(8.33333);
+            stats.close();
             return null;
         }).when(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
 
@@ -750,7 +766,7 @@
     }
 
     @Test
-    public void testAggregateBatteryStats_incompatibleSnapshot() {
+    public void testAggregateBatteryStats_incompatibleSnapshot() throws IOException {
         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
         batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
 
@@ -776,6 +792,8 @@
         when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
                 .thenReturn(span1);
 
+        span1.close();
+
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
                 mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock);
@@ -787,5 +805,7 @@
         assertThat(stats.getCustomPowerComponentNames())
                 .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
         assertThat(stats.getStatsDuration()).isEqualTo(1234);
+
+        stats.close();
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 52675f6..383616e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -323,6 +323,7 @@
     }
 
     private void before() {
+        BatteryUsageStats.DEBUG_INSTANCE_COUNT = true;
         HandlerThread bgThread = new HandlerThread("bg thread");
         bgThread.setUncaughtExceptionHandler((thread, throwable)-> {
             mThrowable = throwable;
@@ -338,6 +339,10 @@
 
     private void after() throws Throwable {
         waitForBackgroundThread();
+        if (mBatteryUsageStats != null) {
+            mBatteryUsageStats.close();
+        }
+        BatteryUsageStats.assertAllInstancesClosed();
     }
 
     public void waitForBackgroundThread() throws Throwable {
@@ -409,6 +414,14 @@
     }
 
     BatteryUsageStats apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) {
+        if (mBatteryUsageStats != null) {
+            try {
+                mBatteryUsageStats.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            mBatteryUsageStats = null;
+        }
         final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames();
         final boolean includePowerModels = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 88624f2..1b6b8c4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -76,22 +76,25 @@
     private static final int APP_UID2 = 314;
 
     @Test
-    public void testBuilder() {
+    public void testBuilder() throws Exception {
         BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(true).build();
         assertBatteryUsageStats1(batteryUsageStats, true);
+        batteryUsageStats.close();
     }
 
     @Test
-    public void testBuilder_noProcessStateData() {
+    public void testBuilder_noProcessStateData() throws Exception {
         BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(false).build();
         assertBatteryUsageStats1(batteryUsageStats, false);
+        batteryUsageStats.close();
     }
 
     @Test
-    public void testParcelability_smallNumberOfUids() {
+    public void testParcelability_smallNumberOfUids() throws Exception {
         final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats1(true).build();
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
+        outBatteryUsageStats.close();
 
         assertThat(parcel.dataSize()).isLessThan(100000);
 
@@ -101,10 +104,11 @@
                 parcel.readParcelable(getClass().getClassLoader());
         assertThat(inBatteryUsageStats).isNotNull();
         assertBatteryUsageStats1(inBatteryUsageStats, true);
+        inBatteryUsageStats.close();
     }
 
     @Test
-    public void testParcelability_largeNumberOfUids() {
+    public void testParcelability_largeNumberOfUids() throws Exception {
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[0]);
 
@@ -141,22 +145,27 @@
             assertThat(uidBatteryConsumer).isNotNull();
             assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(i * 100);
         }
+        inBatteryUsageStats.close();
+        outBatteryUsageStats.close();
     }
 
     @Test
-    public void testDefaultSessionDuration() {
+    public void testDefaultSessionDuration() throws Exception {
         final BatteryUsageStats stats =
                 buildBatteryUsageStats1(true).setStatsDuration(10000).build();
         assertThat(stats.getStatsDuration()).isEqualTo(10000);
+        stats.close();
     }
 
     @Test
-    public void testDump() {
+    public void testDump() throws Exception {
         final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
         final StringWriter out = new StringWriter();
         try (PrintWriter pw = new PrintWriter(out)) {
             stats.dump(pw, "  ");
         }
+        stats.close();
+
         final String dump = out.toString();
 
         assertThat(dump).contains("Capacity: 4000");
@@ -187,12 +196,14 @@
     }
 
     @Test
-    public void testDumpNoScreenOrPowerState() {
+    public void testDumpNoScreenOrPowerState() throws Exception {
         final BatteryUsageStats stats = buildBatteryUsageStats1(true, false, false).build();
         final StringWriter out = new StringWriter();
         try (PrintWriter pw = new PrintWriter(out)) {
             stats.dump(pw, "  ");
         }
+        stats.close();
+
         final String dump = out.toString();
 
         assertThat(dump).contains("Capacity: 4000");
@@ -222,7 +233,7 @@
     }
 
     @Test
-    public void testAdd() {
+    public void testAdd() throws Exception {
         final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
         final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
         final BatteryUsageStats sum =
@@ -261,24 +272,31 @@
         assertAggregateBatteryConsumer(sum,
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
                 40211, 40422, 40633, 40844);
+        stats1.close();
+        stats2.close();
+        sum.close();
     }
 
     @Test
-    public void testAdd_customComponentMismatch() {
+    public void testAdd_customComponentMismatch() throws Exception {
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+        stats.close();
+        builder.discard();
     }
 
     @Test
-    public void testAdd_processStateDataMismatch() {
+    public void testAdd_processStateDataMismatch() throws Exception {
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+        stats.close();
+        builder.discard();
     }
 
     @Test
@@ -290,12 +308,14 @@
         final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
         stats.writeXml(serializer);
         serializer.endDocument();
+        stats.close();
 
         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
         TypedXmlPullParser parser = Xml.newBinaryPullParser();
         parser.setInput(in, StandardCharsets.UTF_8.name());
         final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
         assertBatteryUsageStats1(fromXml, true);
+        fromXml.close();
     }
 
     private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 1e4454c..b374a32 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.power.stats;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.app.usage.NetworkStatsManager;
@@ -80,7 +81,7 @@
             Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
         super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
                 mock(PlatformIdleStateCallback.class), mock(EnergyStatsRetriever.class),
-                mock(UserInfoProvider.class), mock(PowerProfile.class),
+                mock(UserInfoProvider.class), mockPowerProfile(),
                 new CpuScalingPolicies(new SparseArray<>(), new SparseArray<>()),
                 powerStatsUidResolver, mock(FrameworkStatsLogger.class),
                 mock(BatteryStatsHistory.TraceDelegate.class),
@@ -96,6 +97,12 @@
         mKernelWakelockReader = null;
     }
 
+    private static PowerProfile mockPowerProfile() {
+        PowerProfile powerProfile = mock(PowerProfile.class);
+        when(powerProfile.getNumDisplays()).thenReturn(1);
+        return powerProfile;
+    }
+
     public void initMeasuredEnergyStats(String[] customBucketNames) {
         final boolean[] supportedStandardBuckets =
                 new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 7aa2e0f..2b55303 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -19,6 +19,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -50,7 +53,8 @@
             IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
 
     @Mock
-    private Context mContextSpy;
+    private Context mContext;
+    private BackupTransportConnection mBackupTransportConnection;
 
     private ForensicService mForensicService;
     private TestLooper mTestLooper;
@@ -63,7 +67,7 @@
 
         mTestLooper = new TestLooper();
         mLooper = mTestLooper.getLooper();
-        mForensicService = new ForensicService(new MockInjector(mContextSpy));
+        mForensicService = new ForensicService(new MockInjector(mContext));
         mForensicService.onStart();
     }
 
@@ -253,6 +257,8 @@
         assertEquals(STATE_VISIBLE, scb1.mState);
         assertEquals(STATE_VISIBLE, scb2.mState);
 
+        doReturn(true).when(mBackupTransportConnection).initialize();
+
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().enable(ccb);
         mTestLooper.dispatchAll();
@@ -262,6 +268,29 @@
     }
 
     @Test
+    public void testEnable_FromVisible_TwoMonitors_BackupTransportUnavailable()
+            throws RemoteException {
+        mForensicService.setState(STATE_VISIBLE);
+        StateCallback scb1 = new StateCallback();
+        StateCallback scb2 = new StateCallback();
+        mForensicService.getBinderService().monitorState(scb1);
+        mForensicService.getBinderService().monitorState(scb2);
+        mTestLooper.dispatchAll();
+        assertEquals(STATE_VISIBLE, scb1.mState);
+        assertEquals(STATE_VISIBLE, scb2.mState);
+
+        doReturn(false).when(mBackupTransportConnection).initialize();
+
+        CommandCallback ccb = new CommandCallback();
+        mForensicService.getBinderService().enable(ccb);
+        mTestLooper.dispatchAll();
+        assertEquals(STATE_VISIBLE, scb1.mState);
+        assertEquals(STATE_VISIBLE, scb2.mState);
+        assertNotNull(ccb.mErrorCode);
+        assertEquals(ERROR_BACKUP_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue());
+    }
+
+    @Test
     public void testEnable_FromEnabled_TwoMonitors() throws RemoteException {
         mForensicService.setState(STATE_ENABLED);
         StateCallback scb1 = new StateCallback();
@@ -330,6 +359,8 @@
         assertEquals(STATE_ENABLED, scb1.mState);
         assertEquals(STATE_ENABLED, scb2.mState);
 
+        doNothing().when(mBackupTransportConnection).release();
+
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().disable(ccb);
         mTestLooper.dispatchAll();
@@ -356,6 +387,12 @@
             return mLooper;
         }
 
+        @Override
+        public BackupTransportConnection getBackupTransportConnection() {
+            mBackupTransportConnection = spy(new BackupTransportConnection(mContext));
+            return mBackupTransportConnection;
+        }
+
     }
 
     private static class StateCallback extends IForensicServiceStateCallback.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 7829fcc..8df18a8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -480,7 +480,7 @@
         if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
             mFullScreenMagnificationControllerStub.resetAndStubMethods();
             mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
-                    config.getCenterX(), config.getCenterY(), false, SERVICE_ID);
+                    config.getCenterX(), config.getCenterY(), true, false, SERVICE_ID);
             mMagnificationManagerStub.deactivateIfNeed();
         } else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
             mMagnificationManagerStub.resetAndStubMethods();
@@ -531,6 +531,9 @@
             };
             doAnswer(enableMagnificationStubAnswer).when(
                     mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
+                    anyFloat(), anyFloat(), anyBoolean(), anyBoolean(), eq(SERVICE_ID));
+            doAnswer(enableMagnificationStubAnswer).when(
+                    mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
                     anyFloat(), anyFloat(), anyBoolean(), eq(SERVICE_ID));
 
             Answer disableMagnificationStubAnswer = invocation -> {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index c4b4afd..5985abc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
 
+import static com.android.server.accessibility.Flags.FLAG_MAGNIFICATION_ENLARGE_POINTER;
 import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
 import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
 import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
@@ -76,6 +77,7 @@
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 
@@ -126,6 +128,7 @@
     final Resources mMockResources = mock(Resources.class);
     final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
     final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
+    final InputManagerInternal mMockInputManager = mock(InputManagerInternal.class);
     private final MagnificationAnimationCallback mAnimationCallback = mock(
             MagnificationAnimationCallback.class);
     private final MagnificationInfoChangedCallback mRequestObserver = mock(
@@ -163,6 +166,7 @@
         when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
         when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
         when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+        when(mMockControllerCtx.getInputManager()).thenReturn(mMockInputManager);
         when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
         when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
         mResolver = new MockContentResolver();
@@ -285,10 +289,11 @@
                 mFullScreenMagnificationController.magnificationRegionContains(displayId, 100,
                         100));
         assertFalse(mFullScreenMagnificationController.reset(displayId, true));
-        assertFalse(mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+        assertFalse(
+                mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, true, 0));
         assertFalse(mFullScreenMagnificationController.setCenter(displayId, 100, 100, false, 1));
         assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                1.5f, 100, 100, false, 2));
+                1.5f, 100, 100, true, false, 2));
         assertTrue(mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
 
         mFullScreenMagnificationController.getMagnificationRegion(displayId, new Region());
@@ -313,7 +318,7 @@
         final float scale = 2.0f;
         final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         assertFalse(mFullScreenMagnificationController
-                .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+                .setScale(TEST_DISPLAY, scale, center.x, center.y, true, false, SERVICE_ID_1));
         assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
     }
 
@@ -331,7 +336,7 @@
         final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
         assertTrue(mFullScreenMagnificationController
-                .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
+                .setScale(displayId, scale, center.x, center.y, true, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
@@ -357,7 +362,7 @@
         float scale = 2.0f;
         PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         assertTrue(mFullScreenMagnificationController
-                .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+                .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, true, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         // New center should be halfway between original center and pivot
@@ -405,7 +410,7 @@
         float scale = 2.0f;
         assertTrue(mFullScreenMagnificationController.setScale(displayId, scale,
                 INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
-                false, SERVICE_ID_1));
+                true, false, SERVICE_ID_1));
         Mockito.reset(mMockWindowManager);
 
         PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
@@ -440,7 +445,7 @@
         MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
 
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
-                newCenter.x, newCenter.y, mAnimationCallback, SERVICE_ID_1));
+                newCenter.x, newCenter.y, true, mAnimationCallback, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -486,11 +491,11 @@
         final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         final float targetScale = 2.0f;
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                targetScale, center.x, center.y, false, SERVICE_ID_1));
+                targetScale, center.x, center.y, true, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                targetScale, center.x, center.y, mAnimationCallback, SERVICE_ID_1));
+                targetScale, center.x, center.y, true, mAnimationCallback, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         verify(mMockValueAnimator, never()).start();
@@ -516,7 +521,7 @@
 
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
                 MagnificationScaleProvider.MAX_SCALE + 1.0f,
-                newCenter.x, newCenter.y, false, SERVICE_ID_1));
+                newCenter.x, newCenter.y, true, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -527,7 +532,7 @@
         // Verify that we can't zoom below 1x
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, 0.5f,
                 INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
-                false, SERVICE_ID_1));
+                true, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
@@ -551,7 +556,7 @@
 
         // Off the edge to the top and left
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                scale, -100f, -200f, false, SERVICE_ID_1));
+                scale, -100f, -200f, true, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
@@ -565,7 +570,7 @@
         // Off the edge to the bottom and right
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
                 INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
-                false, SERVICE_ID_1));
+                true, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
         newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
@@ -619,7 +624,7 @@
         PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
         // First zoom in
         assertTrue(mFullScreenMagnificationController
-                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
         Mockito.reset(mMockWindowManager);
@@ -673,7 +678,7 @@
         // Upper left edges
         PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
         assertTrue(mFullScreenMagnificationController
-                .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+                .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, true, false,
                         SERVICE_ID_1));
         Mockito.reset(mMockWindowManager);
         MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
@@ -685,7 +690,7 @@
         // Lower right edges
         PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         assertTrue(mFullScreenMagnificationController
-                .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+                .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, true, false,
                         SERVICE_ID_1));
         Mockito.reset(mMockWindowManager);
         MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
@@ -710,7 +715,7 @@
         float scale = 2.0f;
         // First zoom in
         assertTrue(mFullScreenMagnificationController
-                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
@@ -753,7 +758,7 @@
         PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
         // First zoom in
         assertTrue(mFullScreenMagnificationController
-                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
@@ -786,12 +791,12 @@
         register(displayId);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         assertTrue(mFullScreenMagnificationController
-                .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+                .setScale(displayId, 2.0f, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_1));
         assertEquals(SERVICE_ID_1,
                 mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
         assertTrue(mFullScreenMagnificationController
-                .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+                .setScale(displayId, 1.5f, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_2));
         assertEquals(SERVICE_ID_2,
                 mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
@@ -809,10 +814,10 @@
         register(displayId);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         mFullScreenMagnificationController
-                .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+                .setScale(displayId, 2.0f, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_1);
         mFullScreenMagnificationController
-                .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+                .setScale(displayId, 1.5f, startCenter.x, startCenter.y, true, false,
                         SERVICE_ID_2);
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
         checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
@@ -873,7 +878,7 @@
         float scale = 2.5f;
         PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                scale, firstCenter.x, firstCenter.y, mAnimationCallback, SERVICE_ID_1));
+                scale, firstCenter.x, firstCenter.y, true, mAnimationCallback, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
         Mockito.reset(mMockValueAnimator);
         // Stubs the logic after the animation is started.
@@ -1076,7 +1081,7 @@
         float scale = 2.0f;
         // setting animate parameter to true is differ from zoomIn2xToMiddle()
         mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
-                true, SERVICE_ID_1);
+                true, true, SERVICE_ID_1);
         MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         Mockito.reset(mMockWindowManager);
@@ -1107,7 +1112,7 @@
         PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
         float scale = 2.0f;
         mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
-                false, SERVICE_ID_1);
+                true, false, SERVICE_ID_1);
         mMessageCapturingHandler.sendAllMessages();
         MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
@@ -1148,7 +1153,7 @@
         PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
         float scale = 2.0f;
         mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
-                true, SERVICE_ID_1);
+                true, true, SERVICE_ID_1);
         mMessageCapturingHandler.sendAllMessages();
         MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         when(mMockValueAnimator.isRunning()).thenReturn(true);
@@ -1335,7 +1340,7 @@
                 scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
 
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
+                scale, firstCenter.x, firstCenter.y, true, true, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertEquals(firstCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -1404,7 +1409,7 @@
         register(DISPLAY_0);
         final float scale = 1.0f;
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
+                DISPLAY_0, scale, Float.NaN, Float.NaN, true, true, SERVICE_ID_1);
 
         checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ false, DISPLAY_0);
         verify(mMockWindowManager).setFullscreenMagnificationActivated(DISPLAY_0, true);
@@ -1449,7 +1454,7 @@
 
         PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         mFullScreenMagnificationController.setScale(TEST_DISPLAY, 1.0f, pivotPoint.x, pivotPoint.y,
-                false, SERVICE_ID_1);
+                true, false, SERVICE_ID_1);
         mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
 
         // persistScale may post a task to a background thread. Let's wait for it completes.
@@ -1464,10 +1469,10 @@
         register(DISPLAY_1);
         final PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         mFullScreenMagnificationController.setScale(DISPLAY_0, 3.0f, pivotPoint.x, pivotPoint.y,
-                false, SERVICE_ID_1);
+                true, false, SERVICE_ID_1);
         mFullScreenMagnificationController.persistScale(DISPLAY_0);
         mFullScreenMagnificationController.setScale(DISPLAY_1, 4.0f, pivotPoint.x, pivotPoint.y,
-                false, SERVICE_ID_1);
+                true, false, SERVICE_ID_1);
         mFullScreenMagnificationController.persistScale(DISPLAY_1);
 
         // persistScale may post a task to a background thread. Let's wait for it completes.
@@ -1479,6 +1484,101 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+    public void persistScale_setValue_notifyInput() {
+        register(TEST_DISPLAY);
+
+        PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
+                /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+        verify(mMockInputManager, never()).setAccessibilityPointerIconScaleFactor(anyInt(),
+                anyFloat());
+
+        mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+
+        // persistScale may post a task to a background thread. Let's wait for it completes.
+        waitForBackgroundThread();
+        Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
+                4.0f);
+        verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+    public void setScale_setNonTransientScale_notifyInput() {
+        register(TEST_DISPLAY);
+
+        PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
+                /* isScaleTransient= */ false, /* animate= */ false, SERVICE_ID_1);
+
+        verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+    public void setScaleAndCenter_setTransientScale_notNotifyInput() {
+        register(TEST_DISPLAY);
+
+        PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        mFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 3.0f, point.x,
+                point.y, /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+
+        verify(mRequestObserver).onFullScreenMagnificationChanged(anyInt(), any(Region.class),
+                any(MagnificationConfig.class));
+        verify(mMockInputManager, never()).setAccessibilityPointerIconScaleFactor(anyInt(),
+                anyFloat());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+    public void setScaleAndCenter_setNonTransientScale_notifyInput() {
+        register(TEST_DISPLAY);
+
+        PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        mFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 3.0f, point.x,
+                point.y, /* isScaleTransient= */ false, /* animate= */ false, SERVICE_ID_1);
+
+        verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 3.0f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+    public void setCenter_notNotifyInput() {
+        register(TEST_DISPLAY);
+
+        PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        mFullScreenMagnificationController.setScale(TEST_DISPLAY, 2.0f, point.x, point.y,
+                /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+        mFullScreenMagnificationController.setCenter(TEST_DISPLAY, point.x, point.y, false,
+                SERVICE_ID_1);
+
+        // Note that setCenter doesn't change scale, so it's not necessary to notify the input
+        // manager, but we currently do. The input manager skips redundant computation if the
+        // notified scale is the same as the previous call.
+        verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY,
+                2.0f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+    public void offsetMagnifiedRegion_notNotifyInput() {
+        register(TEST_DISPLAY);
+
+        PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        mFullScreenMagnificationController.setScale(TEST_DISPLAY, 2.0f, point.x, point.y,
+                /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+        mFullScreenMagnificationController.offsetMagnifiedRegion(TEST_DISPLAY, 100, 50,
+                SERVICE_ID_1);
+
+        // Note that setCenter doesn't change scale, so it's not necessary to notify the input
+        // manager, but we currently do. The input manager skips redundant computation if the
+        // notified scale is the same as the previous call.
+        verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY,
+                2.0f);
+    }
+
+    @Test
     public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
         setScaleToMagnifying();
 
@@ -1535,7 +1635,7 @@
         PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
 
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, pivotPoint.x, pivotPoint.y,
-                false, SERVICE_ID_1);
+                true, false, SERVICE_ID_1);
     }
 
     private void initMockWindowManager() {
@@ -1578,7 +1678,7 @@
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         float scale = 2.0f;
         mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
-                false, SERVICE_ID_1);
+                true, false, SERVICE_ID_1);
         checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index e5831b3..9f5dd93 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -90,6 +90,7 @@
 import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.testutils.OffsettableClock;
 import com.android.server.testutils.TestHandler;
 import com.android.server.wm.WindowManagerInternal;
@@ -227,9 +228,11 @@
         final FullScreenMagnificationController.ControllerContext mockController =
                 mock(FullScreenMagnificationController.ControllerContext.class);
         final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+        final InputManagerInternal mockInputManager = mock(InputManagerInternal.class);
         when(mockController.getContext()).thenReturn(mContext);
         when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
         when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+        when(mockController.getInputManager()).thenReturn(mockInputManager);
         when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
         when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
         when(mockController.getAnimationDuration()).thenReturn(1000L);
@@ -1343,7 +1346,7 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
                 UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
+                DEFAULT_Y, true, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
 
         mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1364,7 +1367,7 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
                 UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
+                DEFAULT_Y, true, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
 
         mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1401,7 +1404,7 @@
                 mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
 
         mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
+                DEFAULT_Y, true, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
         mMgh.transitionTo(mMgh.mPanningScalingState);
 
@@ -1438,7 +1441,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 5.6f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
         centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
         centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
 
@@ -1530,7 +1533,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 6.2f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
         MotionEvent event = mouseEvent(centerX, centerY, ACTION_HOVER_MOVE);
         send(event, InputDevice.SOURCE_MOUSE);
         fastForward(20);
@@ -1571,7 +1574,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
 
         // HOVER_MOVE should change magnifier viewport.
         MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE);
@@ -1615,7 +1618,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 5.3f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
         MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
         send(event, source);
         fastForward(20);
@@ -1649,7 +1652,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 2.7f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
         MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
         send(event, source);
         fastForward(20);
@@ -1685,7 +1688,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 3.8f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
         centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
         centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
 
@@ -1722,7 +1725,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
         centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
         centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 2528177..8164ef9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -76,6 +76,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.window.flags.Flags;
 
@@ -154,6 +155,8 @@
     private WindowManagerInternal mWindowManagerInternal;
     @Mock
     private WindowManagerInternal.AccessibilityControllerInternal mA11yController;
+    @Mock
+    private InputManagerInternal mInputManagerInternal;
 
     @Mock
     private DisplayManagerInternal mDisplayManagerInternal;
@@ -200,6 +203,7 @@
         when(mControllerCtx.getContext()).thenReturn(mContext);
         when(mControllerCtx.getTraceManager()).thenReturn(mTraceManager);
         when(mControllerCtx.getWindowManager()).thenReturn(mWindowManagerInternal);
+        when(mControllerCtx.getInputManager()).thenReturn(mInputManagerInternal);
         when(mControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
         when(mControllerCtx.getAnimationDuration()).thenReturn(1000L);
         when(mControllerCtx.newValueAnimator()).thenReturn(mValueAnimator);
@@ -417,7 +421,7 @@
         assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
         verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY,
                 DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
-                true, MAGNIFICATION_GESTURE_HANDLER_ID);
+                true, true, MAGNIFICATION_GESTURE_HANDLER_ID);
         verify(mTransitionCallBack).onResult(TEST_DISPLAY, false);
     }
 
@@ -467,7 +471,7 @@
         assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
         verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
                 eq(DEFAULT_SCALE), eq(MAGNIFIED_CENTER_X), eq(MAGNIFIED_CENTER_Y),
-                any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
+                eq(false), any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
     }
 
     @Test
@@ -484,7 +488,7 @@
 
         verify(mScreenMagnificationController, never()).setScaleAndCenter(anyInt(),
                 anyFloat(), anyFloat(), anyFloat(),
-                anyBoolean(), anyInt());
+                anyBoolean(), anyBoolean(), anyInt());
     }
 
     @Test
@@ -546,7 +550,7 @@
                 config, animate, TEST_SERVICE_ID);
         verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
                 /* scale= */ anyFloat(), /* centerX= */ anyFloat(), /* centerY= */ anyFloat(),
-                mCallbackArgumentCaptor.capture(), /* id= */ anyInt());
+                anyBoolean(), mCallbackArgumentCaptor.capture(), /* id= */ anyInt());
         mCallbackArgumentCaptor.getValue().onResult(true);
         mMockConnection.invokeCallbacks();
 
@@ -616,7 +620,7 @@
     @Test
     public void magnifyThroughExternalRequest_showMagnificationButton() {
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE,
-                MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, false, TEST_SERVICE_ID);
+                MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, false, TEST_SERVICE_ID);
 
         // The first time is trigger when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
@@ -638,7 +642,7 @@
         mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
         verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), eq(newScale),
-                anyFloat(), anyFloat(), anyBoolean(), anyInt());
+                anyFloat(), anyFloat(), anyBoolean(), anyBoolean(), anyInt());
         verify(mScreenMagnificationController).persistScale(eq(TEST_DISPLAY));
     }
 
@@ -681,7 +685,7 @@
         final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
                 config.getScale(), config.getCenterX(), config.getCenterY(),
-                true, TEST_SERVICE_ID);
+                true, true, TEST_SERVICE_ID);
 
         // The notify method is triggered when setting magnification enabled.
         // The setScaleAndCenter call should not trigger notify method due to same scale and center.
@@ -930,7 +934,7 @@
     public void onWindowModeActivated_fullScreenIsActivatedByExternal_fullScreenIsDisabled() {
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
                 DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
-                true, TEST_SERVICE_ID);
+                true, true, TEST_SERVICE_ID);
 
         mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
@@ -1317,7 +1321,8 @@
         }
         if (mode == MODE_FULLSCREEN) {
             mScreenMagnificationController.setScaleAndCenter(displayId, DEFAULT_SCALE, centerX,
-                    centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+                    centerY, true, true,
+                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
         } else {
             mMagnificationConnectionManager.enableWindowMagnification(displayId, DEFAULT_SCALE,
                     centerX, centerY, null, TEST_SERVICE_ID);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 9c6412b..a2e6d4c 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -191,98 +191,6 @@
     }
 
     @Test
-    public void updateRuleSet_notAuthorized() throws Exception {
-        makeUsSystemApp();
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-        TestUtils.assertExpectException(
-                SecurityException.class,
-                "Only system packages specified in config_integrityRuleProviderPackages are"
-                        + " allowed to call this method.",
-                () ->
-                        mService.updateRuleSet(
-                                VERSION,
-                                new ParceledListSlice<>(Arrays.asList(rule)),
-                                /* statusReceiver= */ null));
-    }
-
-    @Test
-    public void updateRuleSet_notSystemApp() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp(false);
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-        TestUtils.assertExpectException(
-                SecurityException.class,
-                "Only system packages specified in config_integrityRuleProviderPackages are"
-                        + " allowed to call this method.",
-                () ->
-                        mService.updateRuleSet(
-                                VERSION,
-                                new ParceledListSlice<>(Arrays.asList(rule)),
-                                /* statusReceiver= */ null));
-    }
-
-    @Test
-    public void updateRuleSet_authorized() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-
-        // no SecurityException
-        mService.updateRuleSet(
-                VERSION, new ParceledListSlice<>(Arrays.asList(rule)), mock(IntentSender.class));
-    }
-
-    @Test
-    public void updateRuleSet_correctMethodCall() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        IntentSender mockReceiver = mock(IntentSender.class);
-        List<Rule> rules =
-                Arrays.asList(
-                        new Rule(
-                                IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME),
-                                Rule.DENY));
-
-        mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
-        runJobInHandler();
-
-        verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
-        assertEquals(STATUS_SUCCESS, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
-    }
-
-    @Test
-    public void updateRuleSet_fail() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        doThrow(new IOException()).when(mIntegrityFileManager).writeRules(any(), any(), any());
-        IntentSender mockReceiver = mock(IntentSender.class);
-        List<Rule> rules =
-                Arrays.asList(
-                        new Rule(
-                                IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME),
-                                Rule.DENY));
-
-        mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
-        runJobInHandler();
-
-        verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
-        assertEquals(STATUS_FAILURE, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
-    }
-
-    @Test
     public void broadcastReceiverRegistration() throws Exception {
         allowlistUsAsRuleProvider();
         makeUsSystemApp();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 592eec5..bc01fc4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -260,9 +260,10 @@
     }
 
     private void initAttentionHelper(TestableFlagResolver flagResolver) {
-        mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
-                mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
-                mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+        mAttentionHelper = new NotificationAttentionHelper(getContext(), new Object(),
+                mock(LightsManager.class),mAccessibilityManager, mPackageManager,
+                mUserManager, mUsageStats, mService.mNotificationManagerPrivate,
+                mock(ZenModeHelper.class), flagResolver);
         mAttentionHelper.onSystemReady();
         mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
         mAttentionHelper.setAudioManager(mAudioManager);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index ff2ce15..6dba967 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -41,6 +41,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Locale;
 
 @RunWith(AndroidJUnit4.class)
@@ -62,18 +64,23 @@
         final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
                 | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
         final var data = new byte[] {0x11, 0x22};
-        final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
-        keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
+        final var keyphrases = new ArrayList<SoundTrigger.KeyphraseRecognitionExtra>(2);
+        keyphrases.add(new SoundTrigger.KeyphraseRecognitionExtra(99,
                 RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
                     new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
-                                           new ConfidenceLevel(5000, 80)});
-        keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
+                                           new ConfidenceLevel(5000, 80)}));
+        keyphrases.add(new SoundTrigger.KeyphraseRecognitionExtra(101,
                 RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
                     new ConfidenceLevel(7777, 30),
-                    new ConfidenceLevel(2222, 60)});
+                    new ConfidenceLevel(2222, 60)}));
 
-        var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
-                keyphrases, data, flags);
+        var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
+            .setCaptureRequested(true)
+            .setAllowMultipleTriggers(false) // must be false
+            .setKeyphrases(keyphrases)
+            .setData(data)
+            .setAudioCapabilities(flags)
+            .build();
         assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c30b4bb..c3466b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -139,7 +139,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.provider.DeviceConfig;
 import android.util.MutableBoolean;
 import android.view.DisplayInfo;
@@ -2662,8 +2661,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT)
     public void testSetOrientation_restrictedByTargetSdk() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        makeDisplayLargeScreen(mDisplayContent);
+
         assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false);
         assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
 
@@ -2673,12 +2675,13 @@
     }
 
     private void assertSetOrientation(int targetSdk, int category, boolean expectRotate) {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
-        activity.mTargetSdk = targetSdk;
+        final String packageName = targetSdk <= Build.VERSION_CODES.VANILLA_ICE_CREAM
+                ? mContext.getPackageName() // WmTests uses legacy sdk.
+                : null; // Simulate CUR_DEVELOPMENT by invalid package (see PlatformCompat).
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setComponent(getUniqueComponentName(packageName)).build();
         activity.info.applicationInfo.category = category;
 
-        activity.setVisible(true);
-
         // Assert orientation is unspecified to start.
         assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index c8a3559..08963f1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -307,6 +307,8 @@
     void createNewTaskWithBaseActivity() {
         final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
                 .setCreateActivity(true)
+                // Respect "@ChangeId" according to test package's target sdk.
+                .setPackage(mAtm.mContext.getPackageName())
                 .setDisplay(mDisplayContent).build();
         mTaskStack.push(newTask);
         pushActivity(newTask.getTopNonFinishingActivity());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5c0d424..2bebcc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1081,7 +1081,8 @@
         final DisplayRotation dr = dc.getDisplayRotation();
         spyOn(dr);
         doReturn(false).when(dr).useDefaultSettingsProvider();
-        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setComponent(getUniqueComponentName(mContext.getPackageName())).build();
         app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app);
 
         assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 35c9e3f..f4fa12e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -51,6 +51,7 @@
 import android.app.servertransaction.RefreshCallbackItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -592,6 +593,11 @@
                 .setTask(mTask)
                 .build();
 
+        spyOn(mActivity.info.applicationInfo);
+        // Disable for camera compat.
+        doReturn(false).when(mActivity.info.applicationInfo).isChangeEnabled(
+                ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
+
         spyOn(mActivity.mAtmService.getLifecycleManager());
         spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index cf1dcd0..7e8bd38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -285,6 +285,52 @@
     }
 
     @Test
+    public void testTaskLayerRankFreeform() {
+        mSetFlagsRule.enableFlags(com.android.window.flags.Flags
+                .FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE);
+        final Task[] freeformTasks = new Task[3];
+        final WindowProcessController[] processes = new WindowProcessController[3];
+        for (int i = 0; i < freeformTasks.length; i++) {
+            freeformTasks[i] = new TaskBuilder(mSupervisor)
+                    .setWindowingMode(WINDOWING_MODE_FREEFORM).setCreateActivity(true).build();
+            final ActivityRecord r = freeformTasks[i].getTopMostActivity();
+            r.setState(RESUMED, "test");
+            processes[i] = r.app;
+        }
+        resizeDisplay(mDisplayContent, 2400, 2000);
+        // ---------
+        // | 2 | 1 |
+        // ---------
+        // | 0 |   |
+        // ---------
+        freeformTasks[2].setBounds(0, 0, 1000, 1000);
+        freeformTasks[1].setBounds(1000, 0, 2000, 1000);
+        freeformTasks[0].setBounds(0, 1000, 1000, 2000);
+        mRootWindowContainer.rankTaskLayers();
+        assertEquals(1, freeformTasks[2].mLayerRank);
+        assertEquals(2, freeformTasks[1].mLayerRank);
+        assertEquals(3, freeformTasks[0].mLayerRank);
+        assertFalse("Top doesn't need perceptible hint", (processes[2].getActivityStateFlags()
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+        assertTrue((processes[1].getActivityStateFlags()
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+        assertTrue((processes[0].getActivityStateFlags()
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+
+        // Make task2 occlude half of task0.
+        clearInvocations(mAtm);
+        freeformTasks[2].setBounds(0, 0, 1000, 1500);
+        waitHandlerIdle(mWm.mH);
+        // The process of task0 will demote from perceptible to visible.
+        final int stateFlags0 = processes[0].getActivityStateFlags();
+        assertTrue((stateFlags0
+                & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0);
+        assertFalse((stateFlags0
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+        verify(mAtm).updateOomAdj();
+    }
+
+    @Test
     public void testForceStopPackage() {
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord activity = task.getTopMostActivity();
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 e956e22..e66dfeb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4824,12 +4824,7 @@
         assertFalse(mActivity.isUniversalResizeable());
 
         mDisplayContent.setIgnoreOrientationRequest(true);
-        final int swDp = mDisplayContent.getConfiguration().smallestScreenWidthDp;
-        if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
-            final int height = 100 + (int) (mDisplayContent.getDisplayMetrics().density
-                    * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
-            resizeDisplay(mDisplayContent, 100 + height, height);
-        }
+        makeDisplayLargeScreen(mDisplayContent);
         assertTrue(mActivity.isUniversalResizeable());
     }
 
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 a215c0a..757c358 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1123,6 +1123,15 @@
         displayContent.onRequestedOverrideConfigurationChanged(c);
     }
 
+    static void makeDisplayLargeScreen(DisplayContent displayContent) {
+        final int swDp = displayContent.getConfiguration().smallestScreenWidthDp;
+        if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
+            final int height = 100 + (int) (displayContent.getDisplayMetrics().density
+                    * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
+            resizeDisplay(displayContent, 100 + height, height);
+        }
+    }
+
     /** Used for the tests that assume the display is portrait by default. */
     static void makeDisplayPortrait(DisplayContent displayContent) {
         if (displayContent.mBaseDisplayHeight <= displayContent.mBaseDisplayWidth) {
@@ -1223,7 +1232,14 @@
     }
 
     static ComponentName getUniqueComponentName() {
-        return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+        return getUniqueComponentName(DEFAULT_COMPONENT_PACKAGE_NAME);
+    }
+
+    static ComponentName getUniqueComponentName(String packageName) {
+        if (packageName == null) {
+            packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
+        }
+        return ComponentName.createRelative(packageName,
                 DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
     }
 
@@ -1298,8 +1314,7 @@
         ActivityBuilder setActivityTheme(int theme) {
             mActivityTheme = theme;
             // Use the real package of test so it can get a valid context for theme.
-            mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
-                    DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+            mComponent = getUniqueComponentName(mService.mContext.getPackageName());
             return this;
         }
 
@@ -1743,7 +1758,7 @@
             if (mIntent == null) {
                 mIntent = new Intent();
                 if (mComponent == null) {
-                    mComponent = getUniqueComponentName();
+                    mComponent = getUniqueComponentName(mPackage);
                 }
                 mIntent.setComponent(mComponent);
                 mIntent.setFlags(mFlags);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 44de65a..79b3a7c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -582,6 +582,22 @@
             "android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
 
     /**
+     * Meta-data represents whether the application supports P2P SMS over carrier roaming satellite
+     * which needs manual trigger to connect to satellite. The messaging applications that supports
+     * P2P SMS over carrier roaming satellites should add the following in their AndroidManifest.
+     * {@code
+     * <application
+     *   <meta-data
+     *     android:name="android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT"
+     *     android:value="true"/>
+     * </application>
+     * }
+     * @hide
+     */
+    public static final String METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT =
+            "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
+
+    /**
      * Request to enable or disable the satellite modem and demo mode.
      * If satellite modem and cellular modem cannot work concurrently,
      * then this will disable the cellular modem if satellite modem is enabled,
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index c6855b4..4ac567c 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -285,7 +285,11 @@
 
         val displayRect = getDisplayRect(wmHelper)
 
-        val endX = if (isLeft) displayRect.left else displayRect.right
+        val endX = if (isLeft) {
+            displayRect.left + SNAP_RESIZE_DRAG_INSET
+        } else {
+            displayRect.right - SNAP_RESIZE_DRAG_INSET
+        }
         val endY = displayRect.centerY() / 2
 
         // drag the window to snap resize
@@ -391,6 +395,7 @@
 
     private companion object {
         val TIMEOUT: Duration = Duration.ofSeconds(3)
+        const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
         const val CAPTION: String = "desktop_mode_caption"
         const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view"
         const val MAXIMIZE_MENU: String = "maximize_menu"
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 6742cbe..168141b 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -41,6 +41,7 @@
         "hamcrest-library",
         "junit-params",
         "kotlin-test",
+        "mockito-kotlin-nodeps",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "platform-screenshot-diff-core",
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 7526737..787ae06 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -799,6 +799,27 @@
     }
 
     @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+    fun testMoveToNextDisplay() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "META + CTRL + D -> Move a task to next display",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_D
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+                intArrayOf(KeyEvent.KEYCODE_D),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
     fun testCapsLockPressNotified() {
         val keyGestureController = KeyGestureController(context, testLooper.looper)
         val listener = KeyGestureEventListener()
diff --git a/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt
new file mode 100644
index 0000000..47e7ac7
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import android.view.PointerIcon
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for {@link PointerIconCache}.
+ */
+@Presubmit
+class PointerIconCacheTest {
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+    @Mock
+    private lateinit var defaultDisplay: Display
+
+    private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+    private lateinit var cache: PointerIconCache
+
+    @Before
+    fun setup() {
+        whenever(defaultDisplay.displayId).thenReturn(Display.DEFAULT_DISPLAY)
+
+        context = object : ContextWrapper(InstrumentationRegistry.getInstrumentation().context) {
+            override fun getDisplay() = defaultDisplay
+        }
+
+        testLooper = TestLooper()
+        cache = PointerIconCache(context, native, Handler(testLooper.looper))
+    }
+
+    @Test
+    fun testSetPointerScale() {
+        val defaultBitmap = getDefaultIcon().bitmap
+        cache.setPointerScale(2f)
+
+        testLooper.dispatchAll()
+        verify(native).reloadPointerIcons()
+
+        val bitmap =
+            cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+        assertEquals(defaultBitmap.height * 2, bitmap.height)
+        assertEquals(defaultBitmap.width * 2, bitmap.width)
+    }
+
+    @Test
+    fun testSetAccessibilityScaleFactor() {
+        val defaultBitmap = getDefaultIcon().bitmap
+        cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 4f)
+
+        testLooper.dispatchAll()
+        verify(native).reloadPointerIcons()
+
+        val bitmap =
+            cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+        assertEquals(defaultBitmap.height * 4, bitmap.height)
+        assertEquals(defaultBitmap.width * 4, bitmap.width)
+    }
+
+    @Test
+    fun testSetAccessibilityScaleFactorOnSecondaryDisplay() {
+        val defaultBitmap = getDefaultIcon().bitmap
+        val secondaryDisplayId = Display.DEFAULT_DISPLAY + 1
+        cache.setAccessibilityScaleFactor(secondaryDisplayId, 4f)
+
+        testLooper.dispatchAll()
+        verify(native).reloadPointerIcons()
+
+        val bitmap =
+            cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+        assertEquals(defaultBitmap.height, bitmap.height)
+        assertEquals(defaultBitmap.width, bitmap.width)
+
+        val bitmapSecondary =
+            cache.getLoadedPointerIcon(secondaryDisplayId, PointerIcon.TYPE_ARROW).bitmap
+        assertEquals(defaultBitmap.height * 4, bitmapSecondary.height)
+        assertEquals(defaultBitmap.width * 4, bitmapSecondary.width)
+    }
+
+    @Test
+    fun testSetPointerScaleAndAccessibilityScaleFactor() {
+        val defaultBitmap = getDefaultIcon().bitmap
+        cache.setPointerScale(2f)
+        cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 3f)
+
+        testLooper.dispatchAll()
+        verify(native, times(2)).reloadPointerIcons()
+
+        val bitmap =
+            cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+        assertEquals(defaultBitmap.height * 6, bitmap.height)
+        assertEquals(defaultBitmap.width * 6, bitmap.width)
+    }
+
+    private fun getDefaultIcon() =
+        PointerIcon.getLoadedSystemIcon(context, PointerIcon.TYPE_ARROW, false, 1f)
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
similarity index 96%
rename from tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 6f3deab..2692e12 100644
--- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -40,7 +40,6 @@
 import android.tools.traces.monitors.PerfettoTraceMonitor;
 import android.tools.traces.protolog.ProtoLogTrace;
 import android.tracing.perfetto.DataSource;
-import android.util.proto.ProtoInputStream;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -74,7 +73,7 @@
 @SuppressWarnings("ConstantConditions")
 @Presubmit
 @RunWith(JUnit4.class)
-public class PerfettoProtoLogImplTest {
+public class ProcessedPerfettoProtoLogImplTest {
     private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog";
     private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb";
     private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
@@ -100,7 +99,7 @@
 
     private static ProtoLogViewerConfigReader sReader;
 
-    public PerfettoProtoLogImplTest() throws IOException {
+    public ProcessedPerfettoProtoLogImplTest() throws IOException {
     }
 
     @BeforeClass
@@ -151,7 +150,8 @@
         ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
                 ViewerConfigInputStreamProvider.class);
         Mockito.when(viewerConfigInputStreamProvider.getInputStream())
-                .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()));
+                .thenAnswer(it -> new AutoClosableProtoInputStream(
+                        sViewerConfigBuilder.build().toByteArray()));
 
         sCacheUpdater = () -> {};
         sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
@@ -165,21 +165,16 @@
                     throw new RuntimeException(
                             "Unexpected viewer config file path provided");
                 }
-                return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
+                return new AutoClosableProtoInputStream(sViewerConfigBuilder.build().toByteArray());
             });
         };
         sProtoLogConfigurationService =
                 new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer);
 
-        if (android.tracing.Flags.clientSideProtoLogging()) {
-            sProtoLog = new PerfettoProtoLogImpl(
-                    MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(),
-                    TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
-        } else {
-            sProtoLog = new PerfettoProtoLogImpl(
-                    viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
-                    TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
-        }
+        sProtoLog = new ProcessedPerfettoProtoLogImpl(
+                MOCK_VIEWER_CONFIG_FILE, viewerConfigInputStreamProvider, sReader,
+                () -> sCacheUpdater.run(), TestProtoLogGroup.values(), dataSourceBuilder,
+                sProtoLogConfigurationService);
 
         busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME);
     }
@@ -398,18 +393,17 @@
     }
 
     @Test
-    public void log_logcatEnabledNoMessage() {
+    public void log_logcatEnabledNoMessageThrows() {
         when(sReader.getViewerString(anyLong())).thenReturn(null);
         PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
 
-        implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
-                new Object[]{5});
-
-        verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
-                LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)"));
-        verify(sReader).getViewerString(eq(1234L));
+        var assertion = assertThrows(RuntimeException.class, () ->
+                implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+                    new Object[]{5}));
+        Truth.assertThat(assertion).hasMessageThat()
+                .contains("Failed to decode message for logcat");
     }
 
     @Test
@@ -539,16 +533,12 @@
         PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
                 .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
                 .build();
-        long before;
-        long after;
         try {
             traceMonitor.start();
-            before = SystemClock.elapsedRealtimeNanos();
             sProtoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
                     0b01100100,
                     new Object[]{"test", 1, 0.1, true});
-            after = SystemClock.elapsedRealtimeNanos();
         } finally {
             traceMonitor.stop(mWriter);
         }
@@ -606,7 +596,8 @@
         Truth.assertThat(stacktrace).doesNotContain(DataSource.class.getSimpleName() + ".java");
         Truth.assertThat(stacktrace)
                 .doesNotContain(ProtoLogImpl.class.getSimpleName() + ".java");
-        Truth.assertThat(stacktrace).contains(PerfettoProtoLogImplTest.class.getSimpleName());
+        Truth.assertThat(stacktrace)
+                .contains(ProcessedPerfettoProtoLogImplTest.class.getSimpleName());
         Truth.assertThat(stacktrace).contains("stackTraceTrimmed");
     }
 
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
index 8ecddaa..3d1e208 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -47,12 +47,12 @@
     }
 
     @Test
-    public void throwOnRegisteringDuplicateGroup() {
-        final var assertion = assertThrows(RuntimeException.class,
-                () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2));
+    public void deduplicatesRegisteringDuplicateGroup() {
+        ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2);
 
-        Truth.assertThat(assertion).hasMessageThat().contains("" + TEST_GROUP_1.getId());
-        Truth.assertThat(assertion).hasMessageThat().contains("duplicate");
+        final var instance = ProtoLog.getSingleInstance();
+        Truth.assertThat(instance.getRegisteredGroups())
+                .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
     }
 
     @Test
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index d78ced1..9e029a8 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -19,9 +19,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import android.os.Build;
 import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
 
+import com.google.common.truth.Truth;
+
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,6 +32,8 @@
 
 import perfetto.protos.ProtologCommon;
 
+import java.io.File;
+
 @Presubmit
 @RunWith(JUnit4.class)
 public class ProtoLogViewerConfigReaderTest {
@@ -83,7 +88,7 @@
                 ).build().toByteArray();
 
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
-            () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+            () -> new AutoClosableProtoInputStream(TEST_VIEWER_CONFIG);
 
     private ProtoLogViewerConfigReader mConfig;
 
@@ -123,6 +128,31 @@
     }
 
     @Test
+    public void viewerConfigIsOnDevice() {
+        Assume.assumeFalse(Build.FINGERPRINT.contains("robolectric"));
+
+        final String[] viewerConfigPaths;
+        if (android.tracing.Flags.perfettoProtologTracing()) {
+            viewerConfigPaths = new String[] {
+                    "/system_ext/etc/wmshell.protolog.pb",
+                    "/system/etc/core.protolog.pb",
+            };
+        } else {
+            viewerConfigPaths = new String[] {
+                    "/system_ext/etc/wmshell.protolog.json.gz",
+                    "/system/etc/protolog.conf.json.gz",
+            };
+        }
+
+        for (final var viewerConfigPath : viewerConfigPaths) {
+            File f = new File(viewerConfigPath);
+
+            Truth.assertWithMessage(f.getAbsolutePath() + " exists").that(f.exists()).isTrue();
+        }
+
+    }
+
+    @Test
     public void loadUnloadAndReloadViewerConfig() {
         loadViewerConfig();
         unloadViewerConfig();
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
index caa018d..e163ef4 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
@@ -485,6 +485,7 @@
     "android.net.thread.IConfigurationReceiver",
     "android.net.thread.IOperationalDatasetCallback",
     "android.net.thread.IOperationReceiver",
+    "android.net.thread.IOutputReceiver",
     "android.net.thread.IStateCallback",
     "android.net.thread.IThreadNetworkController",
     "android.net.thread.IThreadNetworkManager",
@@ -757,6 +758,7 @@
     "com.android.server.thread.openthread.IChannelMasksReceiver",
     "com.android.server.thread.openthread.INsdPublisher",
     "com.android.server.thread.openthread.IOtDaemonCallback",
+    "com.android.server.thread.openthread.IOtOutputReceiver",
     "com.android.server.thread.openthread.IOtStatusReceiver",
     "com.google.android.clockwork.ambient.offload.IDisplayOffloadService",
     "com.google.android.clockwork.ambient.offload.IDisplayOffloadTransitionFinishedCallbacks",