Merge "Add test for multiple brightness events" into udc-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e42e526..9eb9d66 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -4156,6 +4156,7 @@
   public static class WindowInfosListenerForTest.WindowInfo {
     field @NonNull public final android.graphics.Rect bounds;
     field public final boolean isTrustedOverlay;
+    field public final boolean isVisible;
     field @NonNull public final String name;
     field @NonNull public final android.os.IBinder windowToken;
   }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index afe375c..96118f6 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -616,6 +616,7 @@
     /** {@hide} */
     public PackageInstaller(IPackageInstaller installer,
             String installerPackageName, String installerAttributionTag, int userId) {
+        Objects.requireNonNull(installer, "installer cannot be null");
         mInstaller = installer;
         mInstallerPackageName = installerPackageName;
         mAttributionTag = installerAttributionTag;
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 820bb1b..4f6bcb6 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -185,37 +185,41 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
         try {
             for (File file : files) {
-                if (isApkFile(file)) {
-                    final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
-                    if (result.isError()) {
-                        return input.error(result);
-                    }
+                if (!isApkFile(file)) {
+                    continue;
+                }
 
-                    final ApkLite lite = result.getResult();
-                    // Assert that all package names and version codes are
-                    // consistent with the first one we encounter.
-                    if (packageName == null) {
-                        packageName = lite.getPackageName();
-                        versionCode = lite.getVersionCode();
-                    } else {
-                        if (!packageName.equals(lite.getPackageName())) {
-                            return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                    "Inconsistent package " + lite.getPackageName() + " in " + file
-                                            + "; expected " + packageName);
-                        }
-                        if (versionCode != lite.getVersionCode()) {
-                            return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                    "Inconsistent version " + lite.getVersionCode() + " in " + file
-                                            + "; expected " + versionCode);
-                        }
-                    }
+                final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
+                if (result.isError()) {
+                    return input.error(result);
+                }
 
-                    // Assert that each split is defined only oncuses-static-libe
-                    if (apks.put(lite.getSplitName(), lite) != null) {
+                final ApkLite lite = result.getResult();
+                // Assert that all package names and version codes are
+                // consistent with the first one we encounter.
+                if (packageName == null) {
+                    packageName = lite.getPackageName();
+                    versionCode = lite.getVersionCode();
+                } else {
+                    if (!packageName.equals(lite.getPackageName())) {
                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                "Split name " + lite.getSplitName()
-                                        + " defined more than once; most recent was " + file);
+                                "Inconsistent package " + lite.getPackageName() + " in " + file
+                                        + "; expected " + packageName);
                     }
+                    if (versionCode != lite.getVersionCode()) {
+                        return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                                "Inconsistent version " + lite.getVersionCode() + " in " + file
+                                        + "; expected " + versionCode);
+                    }
+                }
+
+                // Assert that each split is defined only once
+                ApkLite prev = apks.put(lite.getSplitName(), lite);
+                if (prev != null) {
+                    return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                            "Split name " + lite.getSplitName()
+                                    + " defined more than once; most recent was " + file
+                                    + ", previous was " + prev.getPath());
                 }
             }
             baseApk = apks.remove(null);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e908ced..48b5cac 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -212,14 +212,7 @@
     @GuardedBy("mLock")
     private boolean mFoldedDeviceState;
 
-    private final CameraManager.DeviceStateListener mFoldStateListener =
-            new CameraManager.DeviceStateListener() {
-                @Override
-                public final void onDeviceStateChanged(boolean folded) {
-                    synchronized (mLock) {
-                        mFoldedDeviceState = folded;
-                    }
-                }};
+    private CameraManager.DeviceStateListener mFoldStateListener;
 
     private static final String TAG = "CameraCharacteristics";
 
@@ -245,7 +238,18 @@
     /**
      * Return the device state listener for this Camera characteristics instance
      */
-    CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }
+    CameraManager.DeviceStateListener getDeviceStateListener() {
+        if (mFoldStateListener == null) {
+            mFoldStateListener = new CameraManager.DeviceStateListener() {
+                        @Override
+                        public final void onDeviceStateChanged(boolean folded) {
+                            synchronized (mLock) {
+                                mFoldedDeviceState = folded;
+                            }
+                        }};
+        }
+        return mFoldStateListener;
+    }
 
     /**
      * Overrides the property value
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 51501b5..85f8ca6 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1836,6 +1836,7 @@
                         ctx.getSystemService(DeviceStateManager.class).registerCallback(
                                 new HandlerExecutor(mDeviceStateHandler), mFoldStateListener);
                     } catch (IllegalStateException e) {
+                        mFoldStateListener = null;
                         Log.v(TAG, "Failed to register device state listener!");
                         Log.v(TAG, "Device state dependent characteristics updates will not be" +
                                 "functional!");
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 01e577f..cfbeeff 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -68,12 +68,18 @@
          */
         public final boolean isTrustedOverlay;
 
+        /**
+         * True if the window is visible.
+         */
+        public final boolean isVisible;
+
         WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds,
                 int inputConfig) {
             this.windowToken = windowToken;
             this.name = name;
             this.bounds = bounds;
             this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
+            this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
         }
     }
 
@@ -131,9 +137,6 @@
     private static List<WindowInfo> buildWindowInfos(InputWindowHandle[] windowHandles) {
         var windowInfos = new ArrayList<WindowInfo>(windowHandles.length);
         for (var handle : windowHandles) {
-            if ((handle.inputConfig & InputConfig.NOT_VISIBLE) != 0) {
-                continue;
-            }
             var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
                     handle.frameBottom);
             windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds,
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
index 7cf428a..56f633f 100644
--- a/core/java/com/android/internal/app/AppLocaleCollector.java
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -157,13 +157,13 @@
      * Get a list of system locale that removes all extensions except for the numbering system.
      */
     @VisibleForTesting
-    public List<LocaleStore.LocaleInfo> getSystemCurrentLocales() {
-        List<LocaleStore.LocaleInfo> sysLocales = LocaleStore.getSystemCurrentLocales();
+    public Set<LocaleStore.LocaleInfo> getSystemCurrentLocales() {
+        Set<LocaleStore.LocaleInfo> sysLocales = LocaleStore.getSystemCurrentLocales();
         return sysLocales.stream().filter(
                 // For the locale to be added into the suggestion area, its country could not be
                 // empty.
                 info -> info.getLocale().getCountry().length() > 0).collect(
-                Collectors.toList());
+                Collectors.toSet());
     }
 
     @Override
@@ -225,7 +225,10 @@
         // Add current system language into suggestion list
         if (!isForCountryMode) {
             boolean isCurrentLocale, existsInApp, existsInIme;
-            for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocales()) {
+            // filter out the system locases that are supported by the application.
+            Set<LocaleStore.LocaleInfo> localeInfoSet =
+                    filterSupportedLocales(getSystemCurrentLocales(), result.mAppSupportedLocales);
+            for (LocaleStore.LocaleInfo localeInfo : localeInfoSet) {
                 isCurrentLocale = mAppCurrentLocale != null
                         && localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
                 // Add the system suggestion flag if the localeInfo exists in mAllAppActiveLocales
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index f4b858f..43d263b 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -32,7 +32,6 @@
 import java.io.Serializable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -404,8 +403,8 @@
     /**
      * Returns a list of system locale that removes all extensions except for the numbering system.
      */
-    public static List<LocaleInfo> getSystemCurrentLocales() {
-        List<LocaleInfo> localeList = new ArrayList<>();
+    public static Set<LocaleInfo> getSystemCurrentLocales() {
+        Set<LocaleInfo> localeList = new HashSet<>();
         LocaleList systemLangList = LocaleList.getDefault();
         for(int i = 0; i < systemLangList.size(); i++) {
             Locale sysLocale = getLocaleWithOnlyNumberingSystem(systemLangList.get(i));
diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml
index 8b3667e..2f894b9 100644
--- a/core/res/res/xml/irq_device_map.xml
+++ b/core/res/res/xml/irq_device_map.xml
@@ -28,6 +28,8 @@
             - Wifi: Use this to denote network traffic that uses the wifi transport.
             - Sound_trigger: Use this to denote sound phrase detection, like the ones supported by
         SoundTriggerManager.
+            - Sensor: Use this to denote wakeups due to sensor events.
+            - Cellular_data: Use this to denote network traffic on the cellular transport.
 
         The overlay should use tags <device> and <subsystem> to describe this mapping in the
         following way:
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index c6bb07b..6d99e94 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -45,7 +45,6 @@
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.After;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -67,22 +66,9 @@
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
-    private float mOriginalFontScale = Float.MIN_VALUE;
-
-    @Before
-    public void setup() {
-        mOriginalFontScale = Settings.System.getFloat(
-            InstrumentationRegistry.getInstrumentation().getContext().getContentResolver(),
-            Settings.System.FONT_SCALE,
-            Float.MIN_VALUE
-        );
-    }
-
     @After
     public void teardown() {
-        if (mOriginalFontScale != Float.MIN_VALUE) {
-            setSystemFontScale(mOriginalFontScale);
-        }
+        restoreSystemFontScaleToDefault();
     }
 
     @IwTest(focusArea = "accessibility")
@@ -160,6 +146,28 @@
         );
     }
 
+    private static void restoreSystemFontScaleToDefault() {
+        ShellIdentityUtils.invokeWithShellPermissions(() -> {
+            // TODO(b/279083734): would use Settings.System.resetToDefaults() if it existed
+            Settings.System.putString(
+                    InstrumentationRegistry.getInstrumentation()
+                            .getContext()
+                            .getContentResolver(),
+                    Settings.System.FONT_SCALE,
+                    null,
+                    /* overrideableByRestore= */ true);
+        });
+
+        PollingCheck.waitFor(
+                /* timeout= */ 5000,
+                () -> InstrumentationRegistry.getInstrumentation()
+                                        .getContext()
+                                        .getResources()
+                                        .getConfiguration()
+                                        .fontScale == 1
+        );
+    }
+
     private Matcher<View> withTextSizeInRange(float sizeStartPx, float sizeEndPx) {
         return new BoundedMatcher<>(TextView.class) {
             private static final float TOLERANCE = 0.05f;
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 92c0dab..2a0e476 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2059,6 +2059,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-233530384": {
+      "message": "Content Recording: Incoming session on display %d can't be set since it is already null; the corresponding VirtualDisplay must have already been removed.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+    },
     "-230587670": {
       "message": "SyncGroup %d:  Unfinished container: %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 59f120d..4d87c95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -61,6 +61,9 @@
     @VisibleForTesting
     final ActivityEmbeddingAnimationSpec mAnimationSpec;
 
+    @Nullable
+    private Animator mActiveAnimator;
+
     ActivityEmbeddingAnimationRunner(@NonNull Context context,
             @NonNull ActivityEmbeddingController controller) {
         mController = controller;
@@ -75,8 +78,10 @@
         // applied to make sure the surface is ready.
         final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
                 new ArrayList<>();
-        final Animator animator = createAnimator(info, startTransaction, finishTransaction,
+        final Animator animator = createAnimator(info, startTransaction,
+                finishTransaction,
                 () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
+        mActiveAnimator = animator;
 
         // Start the animation.
         if (!postStartTransactionCallbacks.isEmpty()) {
@@ -98,6 +103,17 @@
         }
     }
 
+    void cancelAnimationFromMerge() {
+        if (mActiveAnimator == null) {
+            Log.e(TAG,
+                    "No active ActivityEmbedding animator running but mergeAnimation is "
+                            + "trying to cancel one."
+            );
+            return;
+        }
+        mActiveAnimator.end();
+    }
+
     /**
      * Sets transition animation scale settings value.
      * @param scale The setting value of transition animation scale.
@@ -153,6 +169,7 @@
                     adapter.onAnimationEnd(t);
                 }
                 t.apply();
+                mActiveAnimator = null;
                 animationFinishCallback.run();
             }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index bfbddbb..fbdbd3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -118,6 +118,13 @@
         return true;
     }
 
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        mAnimationRunner.cancelAnimationFromMerge();
+    }
+
     private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
         final Rect nonClosingEmbeddedArea = new Rect();
         for (int i = changes.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 53a438e..c980906 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -54,10 +54,42 @@
     void setTriggerBack(boolean triggerBack);
 
     /**
-     * Sets the threshold values that defining edge swipe behavior.
-     * @param progressThreshold the max threshold to keep linear progressing back animation.
+     * Sets the threshold values that define edge swipe behavior.<br>
+     * <br>
+     * <h1>How does {@code nonLinearFactor} work?</h1>
+     * <pre>
+     *     screen              screen              screen
+     *     width               width               width
+     *    |——————|            |————————————|      |————————————————————|
+     *           A     B                   A                   B  C    A
+     *  1 +——————+—————+    1 +————————————+    1 +————————————+———————+
+     *    |     /      |      |          —/|      |            | —————/|
+     *    |    /       |      |        —/  |      |           ——/      |
+     *    |   /        |      |      —/    |      |        ——/ |       |
+     *    |  /         |      |    —/      |      |     ——/    |       |
+     *    | /          |      |  —/        |      |  ——/       |       |
+     *    |/           |      |—/          |      |—/          |       |
+     *  0 +————————————+    0 +————————————+    0 +————————————+———————+
+     *                 B                   B                   B
+     * </pre>
+     * Three devices with different widths (smaller, equal, and wider) relative to the progress
+     * threshold are shown in the graphs.<br>
+     * - A is the width of the screen<br>
+     * - B is the progress threshold (horizontal swipe distance where progress is linear)<br>
+     * - C equals B + (A - B) * nonLinearFactor<br>
+     * <br>
+     * If A is less than or equal to B, {@code progress} for the swipe distance between:<br>
+     * - [0, A] will scale linearly between [0, 1].<br>
+     * If A is greater than B, {@code progress} for swipe distance between:<br>
+     * - [0, B] will scale linearly between [0, B / C]<br>
+     * - (B, A] will scale non-linearly and reach 1.
+     *
+     * @param linearDistance up to this distance progress continues linearly. B in the graph above.
+     * @param maxDistance distance at which the progress will be 1f. A in the graph above.
+     * @param nonLinearFactor This value is used to calculate the target if the screen is wider
+     *                        than the progress threshold.
      */
-    void setSwipeThresholds(float progressThreshold);
+    void setSwipeThresholds(float linearDistance, float maxDistance, float nonLinearFactor);
 
     /**
      * Sets the system bar listener to control the system bar color.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 6d879b8..bb543f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -301,9 +301,12 @@
         }
 
         @Override
-        public void setSwipeThresholds(float progressThreshold) {
+        public void setSwipeThresholds(
+                float linearDistance,
+                float maxDistance,
+                float nonLinearFactor) {
             mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds(
-                    progressThreshold));
+                    linearDistance, maxDistance, nonLinearFactor));
         }
 
         @Override
@@ -509,7 +512,7 @@
                 // Constraints - absolute values
                 float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
                 float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
-                float maxX = mTouchTracker.getMaxX(); // px
+                float maxX = mTouchTracker.getMaxDistance(); // px
                 float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px
 
                 // Current state
@@ -605,8 +608,11 @@
         mTouchTracker.setTriggerBack(triggerBack);
     }
 
-    private void setSwipeThresholds(float progressThreshold) {
-        mTouchTracker.setProgressThreshold(progressThreshold);
+    private void setSwipeThresholds(
+            float linearDistance,
+            float maxDistance,
+            float nonLinearFactor) {
+        mTouchTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
     }
 
     private void invokeOrCancelBack() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 7a00f5b..a0ada39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -28,11 +28,13 @@
  * Helper class to record the touch location for gesture and generate back events.
  */
 class TouchTracker {
-    private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
-            "persist.wm.debug.predictive_back_progress_threshold";
-    private static final int PROGRESS_THRESHOLD = SystemProperties
-            .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
-    private float mProgressThreshold;
+    private static final String PREDICTIVE_BACK_LINEAR_DISTANCE_PROP =
+            "persist.wm.debug.predictive_back_linear_distance";
+    private static final int LINEAR_DISTANCE = SystemProperties
+            .getInt(PREDICTIVE_BACK_LINEAR_DISTANCE_PROP, -1);
+    private float mLinearDistance = LINEAR_DISTANCE;
+    private float mMaxDistance;
+    private float mNonLinearFactor;
     /**
      * Location of the latest touch event
      */
@@ -125,17 +127,42 @@
         // the location everytime back is restarted after being cancelled.
         float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
         float deltaX = Math.abs(startX - touchX);
-        float maxX = getMaxX();
-        maxX = maxX == 0 ? 1 : maxX;
-        return MathUtils.constrain(deltaX / maxX, 0, 1);
+        float linearDistance = mLinearDistance;
+        float maxDistance = getMaxDistance();
+        maxDistance = maxDistance == 0 ? 1 : maxDistance;
+        float progress;
+        if (linearDistance < maxDistance) {
+            // Up to linearDistance it behaves linearly, then slowly reaches 1f.
+
+            // maxDistance is composed of linearDistance + nonLinearDistance
+            float nonLinearDistance = maxDistance - linearDistance;
+            float initialTarget = linearDistance + nonLinearDistance * mNonLinearFactor;
+
+            boolean isLinear = deltaX <= linearDistance;
+            if (isLinear) {
+                progress = deltaX / initialTarget;
+            } else {
+                float nonLinearDeltaX = deltaX - linearDistance;
+                float nonLinearProgress = nonLinearDeltaX / nonLinearDistance;
+                float currentTarget = MathUtils.lerp(
+                        /* start = */ initialTarget,
+                        /* stop = */ maxDistance,
+                        /* amount = */ nonLinearProgress);
+                progress = deltaX / currentTarget;
+            }
+        } else {
+            // Always linear behavior.
+            progress = deltaX / maxDistance;
+        }
+        return MathUtils.constrain(progress, 0, 1);
     }
 
     /**
-     * Maximum X value (in pixels).
+     * Maximum distance in pixels.
      * Progress is considered to be completed (1f) when this limit is exceeded.
      */
-    float getMaxX() {
-        return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
+    float getMaxDistance() {
+        return mMaxDistance;
     }
 
     BackMotionEvent createProgressEvent(float progress) {
@@ -149,7 +176,14 @@
                 /* departingAnimationTarget = */ null);
     }
 
-    public void setProgressThreshold(float progressThreshold) {
-        mProgressThreshold = progressThreshold;
+    public void setProgressThresholds(float linearDistance, float maxDistance,
+            float nonLinearFactor) {
+        if (LINEAR_DISTANCE >= 0) {
+            mLinearDistance = LINEAR_DISTANCE;
+        } else {
+            mLinearDistance = linearDistance;
+        }
+        mMaxDistance = maxDistance;
+        mNonLinearFactor = nonLinearFactor;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 9677728..fa21db5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1015,6 +1015,16 @@
         mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo());
     }
 
+    @Override
+    public boolean syncPipSurfaceState(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info);
+        if (pipChange == null) return false;
+        updatePipForUnhandledTransition(pipChange, startTransaction, finishTransaction);
+        return true;
+    }
+
     private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction) {
@@ -1025,10 +1035,12 @@
         final boolean isInPip = mPipTransitionState.isInPip();
         mSurfaceTransactionHelper
                 .crop(startTransaction, leash, destBounds)
-                .round(startTransaction, leash, isInPip);
+                .round(startTransaction, leash, isInPip)
+                .shadow(startTransaction, leash, isInPip);
         mSurfaceTransactionHelper
                 .crop(finishTransaction, leash, destBounds)
-                .round(finishTransaction, leash, isInPip);
+                .round(finishTransaction, leash, isInPip)
+                .shadow(finishTransaction, leash, isInPip);
     }
 
     /** Hides and shows the existing PIP during fixed rotation transition of other activities. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 949d6f5..2fff0e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -240,6 +240,18 @@
             @NonNull final Transitions.TransitionFinishCallback finishCallback) {
     }
 
+    /**
+     * Applies the proper surface states (rounded corners/shadows) to pip surfaces in `info`.
+     * This is intended to be used when PiP is part of another animation but isn't, itself,
+     * animating (eg. unlocking).
+     * @return `true` if there was a pip in `info`.
+     */
+    public boolean syncPipSurfaceState(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        return false;
+    }
+
     /** End the currently-playing PiP animation. */
     public void end() {
     }
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 087e3a2..a95d038 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
@@ -582,7 +582,6 @@
             return;
         }
 
-        prepareEvictChildTasksIfSplitActive(wct);
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
@@ -604,7 +603,6 @@
             return;
         }
 
-        prepareEvictChildTasksIfSplitActive(wct);
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
@@ -625,7 +623,6 @@
             return;
         }
 
-        prepareEvictChildTasksIfSplitActive(wct);
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
@@ -691,7 +688,6 @@
             mMainStage.activate(wct, false /* reparent */);
         }
 
-        prepareEvictChildTasksIfSplitActive(wct);
         mSplitLayout.setDivideRatio(splitRatio);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
@@ -1075,24 +1071,11 @@
         }
 
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
-        prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+        mMainStage.evictNonOpeningChildren(apps, evictWct);
+        mSideStage.evictNonOpeningChildren(apps, evictWct);
         mSyncQueue.queue(evictWct);
     }
 
-
-    /**
-     * Collects all the current child tasks of a specific split and prepares transaction to evict
-     * them to display.
-     */
-    void prepareEvictChildTasks(@SplitPosition int position, WindowContainerTransaction wct) {
-        if (position == mSideStagePosition) {
-            mSideStage.evictAllChildren(wct);
-        } else {
-            mMainStage.evictAllChildren(wct);
-        }
-    }
-
     void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps,
             WindowContainerTransaction wct) {
         if (position == mSideStagePosition) {
@@ -1107,13 +1090,6 @@
         mSideStage.evictInvisibleChildren(wct);
     }
 
-    void prepareEvictChildTasksIfSplitActive(WindowContainerTransaction wct) {
-        if (mMainStage.isActive()) {
-            mMainStage.evictAllChildren(wct);
-            mSideStage.evictAllChildren(wct);
-        }
-    }
-
     Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
             @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
         switch (stage) {
@@ -1500,10 +1476,8 @@
             wct.startTask(taskInfo.taskId,
                     resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
         }
-        // If running background, we need to reparent current top visible task to another stage
-        // and evict all tasks current under its.
+        // If running background, we need to reparent current top visible task to main stage.
         if (!isSplitScreenVisible()) {
-            // Recreate so we need to reset position rather than keep position of background split.
             mMainStage.reparentTopTask(wct);
             prepareSplitLayout(wct);
         }
@@ -2337,7 +2311,7 @@
                 prepareEnterSplitScreen(out);
                 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
                         null /* consumedCallback */, null /* finishedCallback */,
-                        0 /* extraTransitType */, !mIsDropEntering);
+                        TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, !mIsDropEntering);
             }
         }
         return out;
@@ -2604,7 +2578,8 @@
                 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
             if (mainChild == null && sideChild == null) {
                 Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
-                mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */);
+                mSplitTransitions.mPendingEnter.cancel((cancelWct, cancelT)
+                        -> prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, cancelWct));
                 return true;
             }
         } else {
@@ -2614,10 +2589,7 @@
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
                 mSplitTransitions.mPendingEnter.cancel(
-                        (cancelWct, cancelT) -> {
-                            mSideStage.removeAllTasks(cancelWct, dismissTop == STAGE_TYPE_SIDE);
-                            mMainStage.deactivate(cancelWct, dismissTop == STAGE_TYPE_MAIN);
-                        });
+                        (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
                 return true;
             }
         }
@@ -2626,14 +2598,19 @@
         // transitions locally, but remotes (like Launcher) may get confused if they were
         // depending on listener callbacks. This can happen because task-organizer callbacks
         // aren't serialized with transition callbacks.
+        // This usually occurred on app use trampoline launch new task and finish itself.
         // TODO(b/184679596): Find a way to either include task-org information in
         //                    the transition, or synchronize task-org callbacks.
-        if (mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+        final boolean mainNotContainOpenTask =
+                mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId);
+        final boolean sideNotContainOpenTask =
+                sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId);
+        if (mainNotContainOpenTask) {
             Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
                     + " to have been called with " + mainChild.getTaskInfo().taskId
                     + " before startAnimation().");
         }
-        if (sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+        if (sideNotContainOpenTask) {
             Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
                     + " to have been called with " + sideChild.getTaskInfo().taskId
                     + " before startAnimation().");
@@ -2642,10 +2619,18 @@
         final TransitionInfo.Change finalSideChild = sideChild;
         enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
             if (finalMainChild != null) {
-                mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
+                if (!mainNotContainOpenTask) {
+                    mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
+                } else {
+                    mMainStage.evictInvisibleChildren(callbackWct);
+                }
             }
             if (finalSideChild != null) {
-                mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId);
+                if (!sideNotContainOpenTask) {
+                    mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId);
+                } else {
+                    mSideStage.evictInvisibleChildren(callbackWct);
+                }
             }
             if (enterTransition.mResizeAnim) {
                 mShowDecorImmediately = true;
@@ -2904,9 +2889,10 @@
         if (!isSplitScreenVisible()) {
             mIsDropEntering = true;
         }
-        if (!isSplitScreenVisible()) {
+        if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
             // If split running background, exit split first.
-            // TODO(b/280392203) : skip doing this on shell transition once this bug is fixed.
+            // Skip this on shell transition due to we could evict existing tasks on transition
+            // finished.
             exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
         }
         mLogger.enterRequestedByDrag(position, dragSessionId);
@@ -2916,9 +2902,10 @@
      * Sets info to be logged when splitscreen is next entered.
      */
     public void onRequestToSplit(InstanceId sessionId, int enterReason) {
-        if (!isSplitScreenVisible()) {
+        if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
             // If split running background, exit split first.
-            // TODO(b/280392203) : skip doing this on shell transition once this bug is fixed.
+            // Skip this on shell transition due to we could evict existing tasks on transition
+            // finished.
             exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
         }
         mLogger.enterRequested(sessionId, enterReason);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 42633b7..863b5ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -304,8 +304,8 @@
             return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
                     finishCallback);
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
-            return mKeyguardHandler.startAnimation(
-                    transition, info, startTransaction, finishTransaction, finishCallback);
+            return animateKeyguard(mixed, info, startTransaction, finishTransaction,
+                    finishCallback);
         } else {
             mActiveTransitions.remove(mixed);
             throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -557,6 +557,27 @@
         return handled;
     }
 
+    private boolean animateKeyguard(@NonNull final MixedTransition mixed,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        boolean consumed = mKeyguardHandler.startAnimation(
+                mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+        if (!consumed) {
+            return false;
+        }
+        // Sync pip state.
+        if (mPipHandler != null) {
+            // We don't know when to apply `startTransaction` so use a separate transaction here.
+            // This should be fine because these surface properties are independent.
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            mPipHandler.syncPipSurfaceState(info, t, finishTransaction);
+            t.apply();
+        }
+        return true;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index a625346..4fca8b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -65,12 +65,14 @@
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
                 .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
                 .build();
-        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
+        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(),
+                any());
 
         mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
 
         final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
-        verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
+        verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction),
+                eq(mFinishTransaction),
                 finishCallback.capture(), any());
         verify(mStartTransaction).apply();
         verify(mAnimator).start();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 4f4f356..ab1ccd4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -47,6 +47,7 @@
 
     @Mock
     ShellInit mShellInit;
+
     @Mock
     Transitions mTransitions;
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index b8f615a..ba34f1f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -29,9 +29,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.graphics.Rect;
+import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -58,7 +62,8 @@
     @Before
     public void setup() {
         super.setUp();
-        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
+        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(),
+                any());
     }
 
     @Test
@@ -182,6 +187,44 @@
         verifyNoMoreInteractions(mFinishTransaction);
     }
 
+    @UiThreadTest
+    @Test
+    public void testMergeAnimation() {
+        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+                .addChange(createEmbeddedChange(
+                        EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
+                .build();
+
+        final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+        animator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mController.onAnimationFinished(mTransition);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {
+            }
+        });
+        doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
+        mController.startAnimation(mTransition, info, mStartTransaction,
+                mFinishTransaction, mFinishCallback);
+        verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+        mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
+                mTransition,
+                (wct, cb) -> {
+                });
+        verify(mFinishCallback).onTransitionFinished(any(), any());
+    }
+
     @Test
     public void testOnAnimationFinished() {
         // Should not call finish when there is no transition.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
deleted file mode 100644
index d62e660..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ /dev/null
@@ -1,141 +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.wm.shell.back;
-
-import static org.junit.Assert.assertEquals;
-
-import android.window.BackEvent;
-import android.window.BackMotionEvent;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class TouchTrackerTest {
-    private static final float FAKE_THRESHOLD = 400;
-    private static final float INITIAL_X_LEFT_EDGE = 5;
-    private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
-    private TouchTracker mTouchTracker;
-
-    @Before
-    public void setUp() throws Exception {
-        mTouchTracker = new TouchTracker();
-        mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
-    }
-
-    @Test
-    public void generatesProgress_onStart() {
-        mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
-        BackMotionEvent event = mTouchTracker.createStartEvent(null);
-        assertEquals(event.getProgress(), 0f, 0f);
-    }
-
-    @Test
-    public void generatesProgress_leftEdge() {
-        mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
-        float touchX = 10;
-        float velocityX = 0;
-        float velocityY = 0;
-
-        // Pre-commit
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
-
-        // Post-commit
-        touchX += 100;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
-
-        // Cancel
-        touchX -= 10;
-        mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Cancel more
-        touchX -= 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restart
-        touchX += 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restarted, but pre-commit
-        float restartX = touchX;
-        touchX += 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
-
-        // Restarted, post-commit
-        touchX += 10;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
-    }
-
-    @Test
-    public void generatesProgress_rightEdge() {
-        mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
-        float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
-        float velocityX = 0f;
-        float velocityY = 0f;
-
-        // Pre-commit
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
-
-        // Post-commit
-        touchX -= 100;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
-
-        // Cancel
-        touchX += 10;
-        mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Cancel more
-        touchX += 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restart
-        touchX -= 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restarted, but pre-commit
-        float restartX = touchX;
-        touchX -= 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
-
-        // Restarted, post-commit
-        touchX -= 10;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
-    }
-
-    private float getProgress() {
-        return mTouchTracker.createProgressEvent().getProgress();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
new file mode 100644
index 0000000..9088e89
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.back
+
+import android.util.MathUtils
+import android.window.BackEvent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class TouchTrackerTest {
+    private fun linearTouchTracker(): TouchTracker = TouchTracker().apply {
+        setProgressThresholds(MAX_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR)
+    }
+
+    private fun nonLinearTouchTracker(): TouchTracker = TouchTracker().apply {
+        setProgressThresholds(LINEAR_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR)
+    }
+
+    private fun TouchTracker.assertProgress(expected: Float) {
+        val actualProgress = createProgressEvent().progress
+        assertEquals(expected, actualProgress, /* delta = */ 0f)
+    }
+
+    @Test
+    fun generatesProgress_onStart() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        val event = linearTracker.createStartEvent(null)
+        assertEquals(0f, event.progress, 0f)
+    }
+
+    @Test
+    fun generatesProgress_leftEdge() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        var touchX = 10f
+        val velocityX = 0f
+        val velocityY = 0f
+
+        // Pre-commit
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+        // Post-commit
+        touchX += 100f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+        // Cancel
+        touchX -= 10f
+        linearTracker.setTriggerBack(false)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Cancel more
+        touchX -= 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restart
+        touchX += 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restarted, but pre-commit
+        val restartX = touchX
+        touchX += 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE)
+
+        // Restarted, post-commit
+        touchX += 10f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+    }
+
+    @Test
+    fun generatesProgress_rightEdge() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT)
+        var touchX = INITIAL_X_RIGHT_EDGE - 10 // Fake right edge
+        val velocityX = 0f
+        val velocityY = 0f
+        val target = MAX_DISTANCE
+
+        // Pre-commit
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target)
+
+        // Post-commit
+        touchX -= 100f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target)
+
+        // Cancel
+        touchX += 10f
+        linearTracker.setTriggerBack(false)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Cancel more
+        touchX += 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restart
+        touchX -= 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restarted, but pre-commit
+        val restartX = touchX
+        touchX -= 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((restartX - touchX) / target)
+
+        // Restarted, post-commit
+        touchX -= 10f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target)
+    }
+
+    @Test
+    fun generatesNonLinearProgress_leftEdge() {
+        val nonLinearTracker = nonLinearTouchTracker()
+        nonLinearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        var touchX = 10f
+        val velocityX = 0f
+        val velocityY = 0f
+        val linearTarget = LINEAR_DISTANCE + (MAX_DISTANCE - LINEAR_DISTANCE) * NON_LINEAR_FACTOR
+
+        // Pre-commit: linear progress
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget)
+
+        // Post-commit: still linear progress
+        touchX += 100f
+        nonLinearTracker.setTriggerBack(true)
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget)
+
+        // still linear progress
+        touchX = INITIAL_X_LEFT_EDGE + LINEAR_DISTANCE
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget)
+
+        // non linear progress
+        touchX += 10
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        val nonLinearTouch = (touchX - INITIAL_X_LEFT_EDGE) - LINEAR_DISTANCE
+        val nonLinearProgress = nonLinearTouch / NON_LINEAR_DISTANCE
+        val nonLinearTarget = MathUtils.lerp(linearTarget, MAX_DISTANCE, nonLinearProgress)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget)
+    }
+
+    companion object {
+        private const val MAX_DISTANCE = 500f
+        private const val LINEAR_DISTANCE = 400f
+        private const val NON_LINEAR_DISTANCE = MAX_DISTANCE - LINEAR_DISTANCE
+        private const val NON_LINEAR_FACTOR = 0.2f
+        private const val INITIAL_X_LEFT_EDGE = 5f
+        private const val INITIAL_X_RIGHT_EDGE = MAX_DISTANCE - INITIAL_X_LEFT_EDGE
+    }
+}
\ No newline at end of file
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b1d2e33..4759689 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3730,7 +3730,12 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
     public void setA2dpSuspended(boolean enable) {
-        AudioSystem.setParameters("A2dpSuspended=" + enable);
+        final IAudioService service = getService();
+        try {
+            service.setA2dpSuspended(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -3743,7 +3748,12 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
     public void setLeAudioSuspended(boolean enable) {
-        AudioSystem.setParameters("LeAudioSuspended=" + enable);
+        final IAudioService service = getService();
+        try {
+            service.setLeAudioSuspended(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index fe5afc5..7ce189b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -231,6 +231,12 @@
 
     void setBluetoothScoOn(boolean on);
 
+    @EnforcePermission("BLUETOOTH_STACK")
+    void setA2dpSuspended(boolean on);
+
+    @EnforcePermission("BLUETOOTH_STACK")
+    void setLeAudioSuspended(boolean enable);
+
     boolean isBluetoothScoOn();
 
     void setBluetoothA2dpOn(boolean on);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 6f67d68..1b04f18 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -372,13 +372,12 @@
 void LnbClientCallbackImpl::onEvent(const LnbEventType lnbEventType) {
     ALOGV("LnbClientCallbackImpl::onEvent, type=%d", lnbEventType);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject lnb(env->NewLocalRef(mLnbObj));
-    if (!env->IsSameObject(lnb, nullptr)) {
+    ScopedLocalRef lnb(env, env->NewLocalRef(mLnbObj));
+    if (!env->IsSameObject(lnb.get(), nullptr)) {
         env->CallVoidMethod(
-                lnb,
+                lnb.get(),
                 gFields.onLnbEventID,
                 (jint)lnbEventType);
-        env->DeleteLocalRef(lnb);
     } else {
         ALOGE("LnbClientCallbackImpl::onEvent:"
                 "Lnb object has been freed. Ignoring callback.");
@@ -388,17 +387,15 @@
 void LnbClientCallbackImpl::onDiseqcMessage(const vector<uint8_t> &diseqcMessage) {
     ALOGV("LnbClientCallbackImpl::onDiseqcMessage");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject lnb(env->NewLocalRef(mLnbObj));
-    if (!env->IsSameObject(lnb, nullptr)) {
-        jbyteArray array = env->NewByteArray(diseqcMessage.size());
-        env->SetByteArrayRegion(array, 0, diseqcMessage.size(),
+    ScopedLocalRef lnb(env, env->NewLocalRef(mLnbObj));
+    if (!env->IsSameObject(lnb.get(), nullptr)) {
+        ScopedLocalRef array(env, env->NewByteArray(diseqcMessage.size()));
+        env->SetByteArrayRegion(array.get(), 0, diseqcMessage.size(),
                                 reinterpret_cast<const jbyte *>(&diseqcMessage[0]));
         env->CallVoidMethod(
-                lnb,
+                lnb.get(),
                 gFields.onLnbDiseqcMessageID,
-                array);
-        env->DeleteLocalRef(lnb);
-        env->DeleteLocalRef(array);
+                array.get());
     } else {
         ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
                 "Lnb object has been freed. Ignoring callback.");
@@ -422,10 +419,9 @@
 void DvrClientCallbackImpl::onRecordStatus(RecordStatus status) {
     ALOGV("DvrClientCallbackImpl::onRecordStatus");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject dvr(env->NewLocalRef(mDvrObj));
-    if (!env->IsSameObject(dvr, nullptr)) {
-        env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
-        env->DeleteLocalRef(dvr);
+    ScopedLocalRef dvr(env, env->NewLocalRef(mDvrObj));
+    if (!env->IsSameObject(dvr.get(), nullptr)) {
+        env->CallVoidMethod(dvr.get(), gFields.onDvrRecordStatusID, (jint)status);
     } else {
         ALOGE("DvrClientCallbackImpl::onRecordStatus:"
                 "Dvr object has been freed. Ignoring callback.");
@@ -435,10 +431,9 @@
 void DvrClientCallbackImpl::onPlaybackStatus(PlaybackStatus status) {
     ALOGV("DvrClientCallbackImpl::onPlaybackStatus");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject dvr(env->NewLocalRef(mDvrObj));
-    if (!env->IsSameObject(dvr, nullptr)) {
-        env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
-        env->DeleteLocalRef(dvr);
+    ScopedLocalRef dvr(env, env->NewLocalRef(mDvrObj));
+    if (!env->IsSameObject(dvr.get(), nullptr)) {
+        env->CallVoidMethod(dvr.get(), gFields.onDvrPlaybackStatusID, (jint)status);
     } else {
         ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
                 "Dvr object has been freed. Ignoring callback.");
@@ -614,7 +609,7 @@
 }
 
 /////////////// FilterClientCallbackImpl ///////////////////////
-void FilterClientCallbackImpl::getSectionEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getSectionEvent(const jobjectArray& arr, const int size,
                                                const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -624,20 +619,20 @@
     jint sectionNum = sectionEvent.sectionNum;
     jlong dataLength = sectionEvent.dataLength;
 
-    jobject obj = env->NewObject(mSectionEventClass, mSectionEventInitID, tableId, version,
-                                 sectionNum, dataLength);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mSectionEventClass, mSectionEventInitID, tableId,
+                                           version, sectionNum, dataLength));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int size,
                                              const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
-    jobject audioDescriptor = nullptr;
+    ScopedLocalRef<jobject> audioDescriptor(env);
     gAudioPresentationFields.init(env);
-    jobject presentationsJObj = JAudioPresentationInfo::asJobject(env, gAudioPresentationFields);
+    ScopedLocalRef presentationsJObj(env, JAudioPresentationInfo::asJobject(
+        env, gAudioPresentationFields));
     switch (mediaEvent.extraMetaData.getTag()) {
         case DemuxFilterMediaEventExtraMetaData::Tag::audio: {
 
@@ -650,9 +645,9 @@
             jbyte adGainFront = ad.adGainFront;
             jbyte adGainSurround = ad.adGainSurround;
 
-            audioDescriptor = env->NewObject(mAudioDescriptorClass, mAudioDescriptorInitID, adFade,
-                                             adPan, versionTextTag, adGainCenter, adGainFront,
-                                             adGainSurround);
+            audioDescriptor.reset(env->NewObject(mAudioDescriptorClass, mAudioDescriptorInitID,
+                                                 adFade, adPan, versionTextTag, adGainCenter,
+                                                 adGainFront, adGainSurround));
             break;
         }
         case DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations: {
@@ -660,7 +655,7 @@
                     env, gAudioPresentationFields,
                     mediaEvent.extraMetaData
                             .get<DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations>(),
-                    presentationsJObj);
+                    presentationsJObj.get());
             break;
         }
         default: {
@@ -693,31 +688,27 @@
         sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
     }
 
-    jobject obj = env->NewObject(mMediaEventClass, mMediaEventInitID, streamId, isPtsPresent, pts,
-                                 isDtsPresent, dts, dataLength, offset, nullptr, isSecureMemory,
-                                 avDataId, mpuSequenceNumber, isPesPrivateData, sc,
-                                 audioDescriptor, presentationsJObj);
+    ScopedLocalRef obj(env, env->NewObject(mMediaEventClass, mMediaEventInitID, streamId,
+                                           isPtsPresent, pts, isDtsPresent, dts, dataLength,
+                                           offset, nullptr, isSecureMemory, avDataId,
+                                           mpuSequenceNumber, isPesPrivateData, sc,
+                                           audioDescriptor.get(), presentationsJObj.get()));
 
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
         (dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
         sp<MediaEvent> mediaEventSp =
                 new MediaEvent(mFilterClient, dupFromAidl(mediaEvent.avMemory),
-                               mediaEvent.avDataId, dataLength + offset, obj);
+                               mediaEvent.avDataId, dataLength + offset, obj.get());
         mediaEventSp->mAvHandleRefCnt++;
-        env->SetLongField(obj, mMediaEventFieldContextID, (jlong)mediaEventSp.get());
-        mediaEventSp->incStrong(obj);
+        env->SetLongField(obj.get(), mMediaEventFieldContextID, (jlong)mediaEventSp.get());
+        mediaEventSp->incStrong(obj.get());
     }
 
-    env->SetObjectArrayElement(arr, size, obj);
-    if(audioDescriptor != nullptr) {
-        env->DeleteLocalRef(audioDescriptor);
-    }
-    env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(presentationsJObj);
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getPesEvent(const jobjectArray& arr, const int size,
                                            const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -726,13 +717,12 @@
     jint dataLength = pesEvent.dataLength;
     jint mpuSequenceNumber = pesEvent.mpuSequenceNumber;
 
-    jobject obj = env->NewObject(mPesEventClass, mPesEventInitID, streamId, dataLength,
-                                 mpuSequenceNumber);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mPesEventClass, mPesEventInitID, streamId, dataLength,
+                                 mpuSequenceNumber));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getTsRecordEvent(const jobjectArray& arr, const int size,
                                                 const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -764,13 +754,12 @@
     jlong pts = tsRecordEvent.pts;
     jint firstMbInSlice = tsRecordEvent.firstMbInSlice;
 
-    jobject obj = env->NewObject(mTsRecordEventClass, mTsRecordEventInitID, jpid, ts, sc,
-                                 byteNumber, pts, firstMbInSlice);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mTsRecordEventClass, mTsRecordEventInitID, jpid, ts, sc,
+                                 byteNumber, pts, firstMbInSlice));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getMmtpRecordEvent(const jobjectArray& arr, const int size,
                                                   const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -783,13 +772,13 @@
     jint firstMbInSlice = mmtpRecordEvent.firstMbInSlice;
     jlong tsIndexMask = mmtpRecordEvent.tsIndexMask;
 
-    jobject obj = env->NewObject(mMmtpRecordEventClass, mMmtpRecordEventInitID, scHevcIndexMask,
-                                 byteNumber, mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mMmtpRecordEventClass, mMmtpRecordEventInitID,
+                                           scHevcIndexMask, byteNumber, mpuSequenceNumber, pts,
+                                           firstMbInSlice, tsIndexMask));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getDownloadEvent(const jobjectArray& arr, const int size,
                                                 const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -801,25 +790,25 @@
     jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
     jint dataLength = downloadEvent.dataLength;
 
-    jobject obj = env->NewObject(mDownloadEventClass, mDownloadEventInitID, itemId, downloadId,
-                                 mpuSequenceNumber, itemFragmentIndex, lastItemFragmentIndex,
-                                 dataLength);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mDownloadEventClass, mDownloadEventInitID, itemId,
+                                           downloadId, mpuSequenceNumber, itemFragmentIndex,
+                                           lastItemFragmentIndex, dataLength));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getIpPayloadEvent(const jobjectArray& arr, const int size,
                                                  const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
-    const DemuxFilterIpPayloadEvent &ipPayloadEvent = event.get<DemuxFilterEvent::Tag::ipPayload>();
+    const DemuxFilterIpPayloadEvent &ipPayloadEvent =
+        event.get<DemuxFilterEvent::Tag::ipPayload>();
     jint dataLength = ipPayloadEvent.dataLength;
-    jobject obj = env->NewObject(mIpPayloadEventClass, mIpPayloadEventInitID, dataLength);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mIpPayloadEventClass, mIpPayloadEventInitID,
+                                           dataLength));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getTemiEvent(const jobjectArray& arr, const int size,
                                             const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -828,110 +817,108 @@
     jbyte descrTag = temiEvent.descrTag;
     std::vector<uint8_t> descrData = temiEvent.descrData;
 
-    jbyteArray array = env->NewByteArray(descrData.size());
-    env->SetByteArrayRegion(array, 0, descrData.size(), reinterpret_cast<jbyte *>(&descrData[0]));
+    ScopedLocalRef array(env, env->NewByteArray(descrData.size()));
+    env->SetByteArrayRegion(array.get(), 0, descrData.size(),
+                            reinterpret_cast<jbyte *>(&descrData[0]));
 
-    jobject obj = env->NewObject(mTemiEventClass, mTemiEventInitID, pts, descrTag, array);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(array);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mTemiEventClass, mTemiEventInitID, pts, descrTag,
+                                           array.get()));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getScramblingStatusEvent(const jobjectArray& arr, const int size,
                                                         const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     const DemuxFilterMonitorEvent &scramblingStatus =
             event.get<DemuxFilterEvent::Tag::monitorEvent>()
                     .get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
-    jobject obj = env->NewObject(mScramblingStatusEventClass, mScramblingStatusEventInitID,
-                                 scramblingStatus);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mScramblingStatusEventClass,
+                                           mScramblingStatusEventInitID,
+                                           scramblingStatus));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getIpCidChangeEvent(const jobjectArray& arr, const int size,
                                                    const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     const DemuxFilterMonitorEvent &cid = event.get<DemuxFilterEvent::Tag::monitorEvent>()
                                                  .get<DemuxFilterMonitorEvent::Tag::cid>();
-    jobject obj = env->NewObject(mIpCidChangeEventClass, mIpCidChangeEventInitID, cid);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mIpCidChangeEventClass, mIpCidChangeEventInitID, cid));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
-void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getRestartEvent(const jobjectArray& arr, const int size,
                                                const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     const int32_t &startId = event.get<DemuxFilterEvent::Tag::startId>();
-    jobject obj = env->NewObject(mRestartEventClass, mRestartEventInitID, startId);
-    env->SetObjectArrayElement(arr, size, obj);
-    env->DeleteLocalRef(obj);
+    ScopedLocalRef obj(env, env->NewObject(mRestartEventClass, mRestartEventInitID, startId));
+    env->SetObjectArrayElement(arr, size, obj.get());
 }
 
 void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
     ALOGV("FilterClientCallbackImpl::onFilterEvent");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobjectArray array;
+    ScopedLocalRef<jobjectArray> array(env);
 
     if (!events.empty()) {
-        array = env->NewObjectArray(events.size(), mEventClass, nullptr);
+        array.reset(env->NewObjectArray(events.size(), mEventClass, nullptr));
     }
 
     for (int i = 0, arraySize = 0; i < events.size(); i++) {
         const DemuxFilterEvent &event = events[i];
         switch (event.getTag()) {
             case DemuxFilterEvent::Tag::media: {
-                getMediaEvent(array, arraySize, event);
+                getMediaEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::section: {
-                getSectionEvent(array, arraySize, event);
+                getSectionEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::pes: {
-                getPesEvent(array, arraySize, event);
+                getPesEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::tsRecord: {
-                getTsRecordEvent(array, arraySize, event);
+                getTsRecordEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::mmtpRecord: {
-                getMmtpRecordEvent(array, arraySize, event);
+                getMmtpRecordEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::download: {
-                getDownloadEvent(array, arraySize, event);
+                getDownloadEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::ipPayload: {
-                getIpPayloadEvent(array, arraySize, event);
+                getIpPayloadEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::temi: {
-                getTemiEvent(array, arraySize, event);
+                getTemiEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
             case DemuxFilterEvent::Tag::monitorEvent: {
                 switch (event.get<DemuxFilterEvent::Tag::monitorEvent>().getTag()) {
                     case DemuxFilterMonitorEvent::Tag::scramblingStatus: {
-                        getScramblingStatusEvent(array, arraySize, event);
+                        getScramblingStatusEvent(array.get(), arraySize, event);
                         arraySize++;
                         break;
                     }
                     case DemuxFilterMonitorEvent::Tag::cid: {
-                        getIpCidChangeEvent(array, arraySize, event);
+                        getIpCidChangeEvent(array.get(), arraySize, event);
                         arraySize++;
                         break;
                     }
@@ -943,7 +930,7 @@
                 break;
             }
             case DemuxFilterEvent::Tag::startId: {
-                getRestartEvent(array, arraySize, event);
+                getRestartEvent(array.get(), arraySize, event);
                 arraySize++;
                 break;
             }
@@ -953,32 +940,29 @@
             }
         }
     }
-    jobject filter(env->NewLocalRef(mFilterObj));
-    if (!env->IsSameObject(filter, nullptr)) {
+    ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
+    if (!env->IsSameObject(filter.get(), nullptr)) {
         jmethodID methodID = gFields.onFilterEventID;
         if (mSharedFilter) {
             methodID = gFields.onSharedFilterEventID;
         }
-        env->CallVoidMethod(filter, methodID, array);
-        env->DeleteLocalRef(filter);
+        env->CallVoidMethod(filter.get(), methodID, array.get());
     } else {
         ALOGE("FilterClientCallbackImpl::onFilterEvent:"
               "Filter object has been freed. Ignoring callback.");
     }
-    env->DeleteLocalRef(array);
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
     ALOGV("FilterClientCallbackImpl::onFilterStatus");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject filter(env->NewLocalRef(mFilterObj));
-    if (!env->IsSameObject(filter, nullptr)) {
+    ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
+    if (!env->IsSameObject(filter.get(), nullptr)) {
         jmethodID methodID = gFields.onFilterStatusID;
         if (mSharedFilter) {
             methodID = gFields.onSharedFilterStatusID;
         }
-        env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
-        env->DeleteLocalRef(filter);
+        env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
     } else {
         ALOGE("FilterClientCallbackImpl::onFilterStatus:"
               "Filter object has been freed. Ignoring callback.");
@@ -1115,13 +1099,12 @@
     std::scoped_lock<std::mutex> lock(mMutex);
     for (const auto& mapEntry : mListenersMap) {
         ALOGV("JTuner:%p, jweak:%p", mapEntry.first, mapEntry.second);
-        jobject frontend(env->NewLocalRef(mapEntry.second));
-        if (!env->IsSameObject(frontend, nullptr)) {
+        ScopedLocalRef frontend(env, env->NewLocalRef(mapEntry.second));
+        if (!env->IsSameObject(frontend.get(), nullptr)) {
             env->CallVoidMethod(
-                    frontend,
+                    frontend.get(),
                     gFields.onFrontendEventID,
                     (jint)frontendEventType);
-            env->DeleteLocalRef(frontend);
         } else {
             ALOGW("FrontendClientCallbackImpl::onEvent:"
                     "Frontend object has been freed. Ignoring callback.");
@@ -1133,20 +1116,18 @@
         FrontendScanMessageType type, const FrontendScanMessage& message) {
     ALOGV("FrontendClientCallbackImpl::onScanMessage, type=%d", type);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass clazz = env->FindClass("android/media/tv/tuner/Tuner");
+    ScopedLocalRef clazz(env, env->FindClass("android/media/tv/tuner/Tuner"));
 
     std::scoped_lock<std::mutex> lock(mMutex);
     for (const auto& mapEntry : mListenersMap) {
-        jobject frontend(env->NewLocalRef(mapEntry.second));
-        if (env->IsSameObject(frontend, nullptr)) {
+        ScopedLocalRef frontend(env, env->NewLocalRef(mapEntry.second));
+        if (env->IsSameObject(frontend.get(), nullptr)) {
             ALOGE("FrontendClientCallbackImpl::onScanMessage:"
                     "Tuner object has been freed. Ignoring callback.");
             continue;
         }
-        executeOnScanMessage(env, clazz, frontend, type, message);
-        env->DeleteLocalRef(frontend);
+        executeOnScanMessage(env, clazz.get(), frontend.get(), type, message);
     }
-    env->DeleteLocalRef(clazz);
 }
 
 void FrontendClientCallbackImpl::executeOnScanMessage(
@@ -1183,20 +1164,19 @@
         }
         case FrontendScanMessageType::FREQUENCY: {
             std::vector<int64_t> v = message.get<FrontendScanMessage::Tag::frequencies>();
-            jlongArray freqs = env->NewLongArray(v.size());
-            env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
+            ScopedLocalRef freqs(env, env->NewLongArray(v.size()));
+            env->SetLongArrayRegion(freqs.get(), 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
-                                freqs);
-            env->DeleteLocalRef(freqs);
+                                freqs.get());
             break;
         }
         case FrontendScanMessageType::SYMBOL_RATE: {
             std::vector<int32_t> v = message.get<FrontendScanMessage::Tag::symbolRates>();
-            jintArray symbolRates = env->NewIntArray(v.size());
-            env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+            ScopedLocalRef symbolRates(env, env->NewIntArray(v.size()));
+            env->SetIntArrayRegion(symbolRates.get(), 0, v.size(),
+                                   reinterpret_cast<jint *>(&v[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
-                                symbolRates);
-            env->DeleteLocalRef(symbolRates);
+                                symbolRates.get());
             break;
         }
         case FrontendScanMessageType::HIERARCHY: {
@@ -1211,27 +1191,29 @@
         }
         case FrontendScanMessageType::PLP_IDS: {
             std::vector<int32_t> jintV = message.get<FrontendScanMessage::Tag::plpIds>();
-            jintArray plpIds = env->NewIntArray(jintV.size());
-            env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
-            env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
-            env->DeleteLocalRef(plpIds);
+            ScopedLocalRef plpIds(env, env->NewIntArray(jintV.size()));
+            env->SetIntArrayRegion(plpIds.get(), 0, jintV.size(),
+                                   reinterpret_cast<jint *>(&jintV[0]));
+            env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"),
+                                plpIds.get());
             break;
         }
         case FrontendScanMessageType::GROUP_IDS: {
             std::vector<int32_t> jintV = message.get<FrontendScanMessage::groupIds>();
-            jintArray groupIds = env->NewIntArray(jintV.size());
-            env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
-            env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
-            env->DeleteLocalRef(groupIds);
+            ScopedLocalRef groupIds(env, env->NewIntArray(jintV.size()));
+            env->SetIntArrayRegion(groupIds.get(), 0, jintV.size(),
+                                   reinterpret_cast<jint *>(&jintV[0]));
+            env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"),
+                                groupIds.get());
             break;
         }
         case FrontendScanMessageType::INPUT_STREAM_IDS: {
             std::vector<int32_t> jintV = message.get<FrontendScanMessage::inputStreamIds>();
-            jintArray streamIds = env->NewIntArray(jintV.size());
-            env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
+            ScopedLocalRef streamIds(env, env->NewIntArray(jintV.size()));
+            env->SetIntArrayRegion(streamIds.get(), 0, jintV.size(),
+                                   reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
-                                streamIds);
-            env->DeleteLocalRef(streamIds);
+                                streamIds.get());
             break;
         }
         case FrontendScanMessageType::STANDARD: {
@@ -1254,26 +1236,25 @@
             break;
         }
         case FrontendScanMessageType::ATSC3_PLP_INFO: {
-            jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo");
-            jmethodID init = env->GetMethodID(plpClazz, "<init>", "(IZ)V");
+            ScopedLocalRef plpClazz(env,
+                    env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo"));
+            jmethodID init = env->GetMethodID(plpClazz.get(), "<init>", "(IZ)V");
             std::vector<FrontendScanAtsc3PlpInfo> plpInfos =
                     message.get<FrontendScanMessage::atsc3PlpInfos>();
-            jobjectArray array = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+            ScopedLocalRef array(env,
+                                 env->NewObjectArray(plpInfos.size(), plpClazz.get(), nullptr));
             for (int i = 0; i < plpInfos.size(); i++) {
                 const FrontendScanAtsc3PlpInfo &info = plpInfos[i];
                 jint plpId = info.plpId;
                 jboolean lls = info.bLlsFlag;
-                jobject obj = env->NewObject(plpClazz, init, plpId, lls);
-                env->SetObjectArrayElement(array, i, obj);
-                env->DeleteLocalRef(obj);
+                ScopedLocalRef obj(env, env->NewObject(plpClazz.get(), init, plpId, lls));
+                env->SetObjectArrayElement(array.get(), i, obj.get());
             }
             env->CallVoidMethod(frontend,
                                 env->GetMethodID(clazz, "onAtsc3PlpInfos",
                                                  "([Landroid/media/tv/tuner/frontend/"
                                                  "Atsc3PlpInfo;)V"),
-                                array);
-            env->DeleteLocalRef(array);
-            env->DeleteLocalRef(plpClazz);
+                                array.get());
             break;
         }
         case FrontendScanMessageType::MODULATION: {
@@ -1341,11 +1322,12 @@
         }
         case FrontendScanMessageType::DVBT_CELL_IDS: {
             std::vector<int32_t> jintV = message.get<FrontendScanMessage::dvbtCellIds>();
-            jintArray cellIds = env->NewIntArray(jintV.size());
-            env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
-            env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
-                                cellIds);
-            env->DeleteLocalRef(cellIds);
+            ScopedLocalRef cellIds(env, env->NewIntArray(jintV.size()));
+            env->SetIntArrayRegion(cellIds.get(), 0, jintV.size(),
+                                   reinterpret_cast<jint *>(&jintV[0]));
+            env->CallVoidMethod(frontend,
+                                env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
+                                cellIds.get());
             break;
         }
         default:
@@ -1434,7 +1416,8 @@
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass arrayListClazz = env->FindClass("java/util/ArrayList");
     jmethodID arrayListAdd = env->GetMethodID(arrayListClazz, "add", "(Ljava/lang/Object;)Z");
-    jobject obj = env->NewObject(arrayListClazz, env->GetMethodID(arrayListClazz, "<init>", "()V"));
+    jobject obj = env->NewObject(arrayListClazz,
+                                 env->GetMethodID(arrayListClazz, "<init>", "()V"));
 
     jclass integerClazz = env->FindClass("java/lang/Integer");
     jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V");
@@ -1672,7 +1655,7 @@
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendInfo");
     jmethodID infoInit =
             env->GetMethodID(clazz, "<init>",
-                             "(IIJJIIJI[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V");
+                    "(IIJJIIJI[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V");
 
     jint type = (jint)feInfo->type;
     jlong minFrequency = feInfo->minFrequency;
@@ -1812,9 +1795,8 @@
     jmethodID init = env->GetMethodID(clazz, "<init>", "(II)V");
     jobjectArray valObj = env->NewObjectArray(size, clazz, nullptr);
     for (int i = 0; i < size; i++) {
-        jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
-        env->SetObjectArrayElement(valObj, i, readinessObj);
-        env->DeleteLocalRef(readinessObj);
+        ScopedLocalRef readinessObj(env, env->NewObject(clazz, init, intTypes[i], readiness[i]));
+        env->SetObjectArrayElement(valObj, i, readinessObj.get());
     }
     return valObj;
 }
@@ -2260,79 +2242,72 @@
         switch (s.getTag()) {
             case FrontendStatus::Tag::isDemodLocked: {
                 jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isDemodLocked>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env,
+                        env->NewObject(booleanClazz, initBoolean,
+                                       s.get<FrontendStatus::Tag::isDemodLocked>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::snr: {
                 jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::ber: {
                 jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::per: {
                 jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::preBer: {
                 jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::signalQuality: {
                 jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;");
-                jobject newIntegerObj = env->NewObject(intClazz, initInt,
-                                                       s.get<FrontendStatus::Tag::signalQuality>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt,
+                                       s.get<FrontendStatus::Tag::signalQuality>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::signalStrength: {
                 jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       s.get<FrontendStatus::Tag::signalStrength>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                       s.get<FrontendStatus::Tag::signalStrength>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::symbolRate: {
                 jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt,
+                                       s.get<FrontendStatus::Tag::symbolRate>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::innerFec: {
                 jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;");
-                jclass longClazz = env->FindClass("java/lang/Long");
-                jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V");
-                jobject newLongObj =
-                        env->NewObject(longClazz, initLong,
-                                       static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
-                env->SetObjectField(statusObj, field, newLongObj);
-                env->DeleteLocalRef(newLongObj);
-                env->DeleteLocalRef(longClazz);
+                ScopedLocalRef longClazz(env, env->FindClass("java/lang/Long"));
+                jmethodID initLong = env->GetMethodID(longClazz.get(), "<init>", "(J)V");
+                ScopedLocalRef newLongObj(env,
+                        env->NewObject(longClazz.get(), initLong,
+                                       static_cast<long>(s.get<FrontendStatus::Tag::innerFec>())));
+                env->SetObjectField(statusObj, field, newLongObj.get());
                 break;
             }
             case FrontendStatus::Tag::modulationStatus: {
@@ -2373,139 +2348,128 @@
                     }
                 }
                 if (valid) {
-                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
-                    env->SetObjectField(statusObj, field, newIntegerObj);
-                    env->DeleteLocalRef(newIntegerObj);
+                    ScopedLocalRef newIntegerObj(env,
+                            env->NewObject(intClazz, initInt, intModulation));
+                    env->SetObjectField(statusObj, field, newIntegerObj.get());
                 }
                 break;
             }
             case FrontendStatus::Tag::inversion: {
                 jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                static_cast<jint>(s.get<FrontendStatus::Tag::inversion>())));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::lnbVoltage: {
                 jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>())));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::plpId: {
                 jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::isEWBS: {
                 jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isEWBS>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+                                                       s.get<FrontendStatus::Tag::isEWBS>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::agc: {
                 jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::isLnaOn: {
                 jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isLnaOn>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+                                                       s.get<FrontendStatus::Tag::isLnaOn>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::isLayerError: {
                 jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z");
                 vector<bool> layerErr = s.get<FrontendStatus::Tag::isLayerError>();
 
-                jbooleanArray valObj = env->NewBooleanArray(layerErr.size());
+                ScopedLocalRef valObj(env, env->NewBooleanArray(layerErr.size()));
 
                 for (size_t i = 0; i < layerErr.size(); i++) {
                     jboolean x = layerErr[i];
-                    env->SetBooleanArrayRegion(valObj, i, 1, &x);
+                    env->SetBooleanArrayRegion(valObj.get(), i, 1, &x);
                 }
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::mer: {
                 jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::freqOffset: {
                 jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Long;");
-                jobject newLongObj = env->NewObject(longClazz, initLong,
-                                                    s.get<FrontendStatus::Tag::freqOffset>());
-                env->SetObjectField(statusObj, field, newLongObj);
-                env->DeleteLocalRef(newLongObj);
+                ScopedLocalRef newLongObj(env, env->NewObject(longClazz, initLong,
+                                                    s.get<FrontendStatus::Tag::freqOffset>()));
+                env->SetObjectField(statusObj, field, newLongObj.get());
                 break;
             }
             case FrontendStatus::Tag::hierarchy: {
                 jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>())));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::isRfLocked: {
                 jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isRfLocked>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+                                                       s.get<FrontendStatus::Tag::isRfLocked>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::plpInfo: {
                 jfieldID field = env->GetFieldID(clazz, "mPlpInfo",
                         "[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;");
-                jclass plpClazz = env->FindClass(
-                        "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo");
-                jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V");
+                ScopedLocalRef plpClazz(env, env->FindClass(
+                        "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo"));
+                jmethodID initPlp = env->GetMethodID(plpClazz.get(), "<init>", "(IZI)V");
 
-                vector<FrontendStatusAtsc3PlpInfo> plpInfos = s.get<FrontendStatus::Tag::plpInfo>();
-                jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+                vector<FrontendStatusAtsc3PlpInfo> plpInfos =
+                        s.get<FrontendStatus::Tag::plpInfo>();
+                ScopedLocalRef valObj(env, env->NewObjectArray(plpInfos.size(), plpClazz.get(),
+                                                               nullptr));
                 for (int i = 0; i < plpInfos.size(); i++) {
                     const FrontendStatusAtsc3PlpInfo &info = plpInfos[i];
                     jint plpId = info.plpId;
                     jboolean isLocked = info.isLocked;
                     jint uec = info.uec;
 
-                    jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
-                    env->SetObjectArrayElement(valObj, i, plpObj);
-                    env->DeleteLocalRef(plpObj);
+                    ScopedLocalRef plpObj(env, env->NewObject(plpClazz.get(), initPlp, plpId,
+                                                              isLocked, uec));
+                    env->SetObjectArrayElement(valObj.get(), i, plpObj.get());
                 }
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
-                env->DeleteLocalRef(plpClazz);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::modulations: {
                 jfieldID field = env->GetFieldID(clazz, "mModulationsExt", "[I");
                 std::vector<FrontendModulation> v = s.get<FrontendStatus::Tag::modulations>();
 
-                jintArray valObj = env->NewIntArray(v.size());
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
                 bool valid = false;
                 jint m[1];
                 for (int i = 0; i < v.size(); i++) {
@@ -2514,63 +2478,63 @@
                         case FrontendModulation::Tag::dvbc: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::dvbc>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::dvbs: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::dvbs>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                            break;
                         }
                         case FrontendModulation::Tag::dvbt: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::dvbt>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::isdbs: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::isdbs>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::isdbs3: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::isdbs3>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::isdbt: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::isdbt>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::atsc: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::atsc>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::atsc3: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::atsc3>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
                         case FrontendModulation::Tag::dtmb: {
                             m[0] = static_cast<jint>(
                                     modulation.get<FrontendModulation::Tag::dtmb>());
-                            env->SetIntArrayRegion(valObj, i, 1, m);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, m);
                             valid = true;
                             break;
                         }
@@ -2579,31 +2543,28 @@
                     }
                 }
                 if (valid) {
-                    env->SetObjectField(statusObj, field, valObj);
+                    env->SetObjectField(statusObj, field, valObj.get());
                 }
-                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::bers: {
                 jfieldID field = env->GetFieldID(clazz, "mBers", "[I");
                 std::vector<int32_t> v = s.get<FrontendStatus::Tag::bers>();
 
-                jintArray valObj = env->NewIntArray(v.size());
-                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+                env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::codeRates: {
                 jfieldID field = env->GetFieldID(clazz, "mCodeRates", "[I");
                 std::vector<FrontendInnerFec> v = s.get<FrontendStatus::Tag::codeRates>();
 
-                jintArray valObj = env->NewIntArray(v.size());
-                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+                env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::bandwidth: {
@@ -2642,9 +2603,9 @@
                         break;
                 }
                 if (valid) {
-                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
-                    env->SetObjectField(statusObj, field, newIntegerObj);
-                    env->DeleteLocalRef(newIntegerObj);
+                    ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+                                                                     intBandwidth));
+                    env->SetObjectField(statusObj, field, newIntegerObj.get());
                 }
                 break;
             }
@@ -2655,8 +2616,8 @@
                 bool valid = true;
                 switch (interval.getTag()) {
                     case FrontendGuardInterval::Tag::dvbt: {
-                        intInterval =
-                                static_cast<jint>(interval.get<FrontendGuardInterval::Tag::dvbt>());
+                        intInterval = static_cast<jint>(
+                                interval.get<FrontendGuardInterval::Tag::dvbt>());
                         break;
                     }
                     case FrontendGuardInterval::Tag::isdbt: {
@@ -2665,8 +2626,8 @@
                         break;
                     }
                     case FrontendGuardInterval::Tag::dtmb: {
-                        intInterval =
-                                static_cast<jint>(interval.get<FrontendGuardInterval::Tag::dtmb>());
+                        intInterval = static_cast<jint>(
+                                interval.get<FrontendGuardInterval::Tag::dtmb>());
                         break;
                     }
                     default:
@@ -2674,14 +2635,15 @@
                         break;
                 }
                 if (valid) {
-                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
-                    env->SetObjectField(statusObj, field, newIntegerObj);
-                    env->DeleteLocalRef(newIntegerObj);
+                    ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+                                                                     intInterval));
+                    env->SetObjectField(statusObj, field, newIntegerObj.get());
                 }
                 break;
             }
             case FrontendStatus::Tag::transmissionMode: {
-                jfieldID field = env->GetFieldID(clazz, "mTransmissionMode", "Ljava/lang/Integer;");
+                jfieldID field = env->GetFieldID(clazz, "mTransmissionMode",
+                                                 "Ljava/lang/Integer;");
                 const FrontendTransmissionMode &transmissionMode =
                         s.get<FrontendStatus::Tag::transmissionMode>();
                 jint intTransmissionMode;
@@ -2707,32 +2669,30 @@
                         break;
                 }
                 if (valid) {
-                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
-                    env->SetObjectField(statusObj, field, newIntegerObj);
-                    env->DeleteLocalRef(newIntegerObj);
+                    ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+                                                                     intTransmissionMode));
+                    env->SetObjectField(statusObj, field, newIntegerObj.get());
                 }
                 break;
             }
             case FrontendStatus::Tag::uec: {
                 jfieldID field = env->GetFieldID(clazz, "mUec", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::systemId: {
                 jfieldID field = env->GetFieldID(clazz, "mSystemId", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::interleaving: {
                 jfieldID field = env->GetFieldID(clazz, "mInterleaving", "[I");
                 std::vector<FrontendInterleaveMode> v = s.get<FrontendStatus::Tag::interleaving>();
-                jintArray valObj = env->NewIntArray(v.size());
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
                 bool valid = false;
                 jint in[1];
                 for (int i = 0; i < v.size(); i++) {
@@ -2741,28 +2701,28 @@
                         case FrontendInterleaveMode::Tag::atsc3: {
                             in[0] = static_cast<jint>(
                                     interleaving.get<FrontendInterleaveMode::Tag::atsc3>());
-                            env->SetIntArrayRegion(valObj, i, 1, in);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, in);
                             valid = true;
                             break;
                         }
                         case FrontendInterleaveMode::Tag::dvbc: {
                             in[0] = static_cast<jint>(
                                     interleaving.get<FrontendInterleaveMode::Tag::dvbc>());
-                            env->SetIntArrayRegion(valObj, i, 1, in);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, in);
                             valid = true;
                            break;
                         }
                         case FrontendInterleaveMode::Tag::dtmb: {
                             in[0] = static_cast<jint>(
                                     interleaving.get<FrontendInterleaveMode::Tag::dtmb>());
-                            env->SetIntArrayRegion(valObj, i, 1, in);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, in);
                             valid = true;
                            break;
                         }
                         case FrontendInterleaveMode::Tag::isdbt: {
                             in[0] = static_cast<jint>(
                                     interleaving.get<FrontendInterleaveMode::Tag::isdbt>());
-                            env->SetIntArrayRegion(valObj, i, 1, in);
+                            env->SetIntArrayRegion(valObj.get(), i, 1, in);
                             valid = true;
                             break;
                         }
@@ -2771,31 +2731,28 @@
                     }
                 }
                 if (valid) {
-                    env->SetObjectField(statusObj, field, valObj);
+                    env->SetObjectField(statusObj, field, valObj.get());
                 }
-                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::isdbtSegment: {
                 jfieldID field = env->GetFieldID(clazz, "mIsdbtSegment", "[I");
                 std::vector<int32_t> v = s.get<FrontendStatus::Tag::isdbtSegment>();
 
-                jintArray valObj = env->NewIntArray(v.size());
-                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+                env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint*>(&v[0]));
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::tsDataRate: {
                 jfieldID field = env->GetFieldID(clazz, "mTsDataRate", "[I");
                 std::vector<int32_t> v = s.get<FrontendStatus::Tag::tsDataRate>();
 
-                jintArray valObj = env->NewIntArray(v.size());
-                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+                env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::rollOff: {
@@ -2813,7 +2770,8 @@
                         break;
                     }
                     case FrontendRollOff::Tag::isdbs3: {
-                        intRollOff = static_cast<jint>(rollOff.get<FrontendRollOff::Tag::isdbs3>());
+                        intRollOff = static_cast<jint>(
+                                rollOff.get<FrontendRollOff::Tag::isdbs3>());
                         break;
                     }
                     default:
@@ -2821,141 +2779,135 @@
                         break;
                 }
                 if (valid) {
-                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
-                    env->SetObjectField(statusObj, field, newIntegerObj);
-                    env->DeleteLocalRef(newIntegerObj);
+                    ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+                                                                     intRollOff));
+                    env->SetObjectField(statusObj, field, newIntegerObj.get());
                 }
                 break;
             }
             case FrontendStatus::Tag::isMiso: {
                 jfieldID field = env->GetFieldID(clazz, "mIsMisoEnabled", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isMiso>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+                                                       s.get<FrontendStatus::Tag::isMiso>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::isLinear: {
                 jfieldID field = env->GetFieldID(clazz, "mIsLinear", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isLinear>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+                                                       s.get<FrontendStatus::Tag::isLinear>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::isShortFrames: {
                 jfieldID field = env->GetFieldID(clazz, "mIsShortFrames", "Ljava/lang/Boolean;");
-                jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
-                                                       s.get<FrontendStatus::Tag::isShortFrames>());
-                env->SetObjectField(statusObj, field, newBooleanObj);
-                env->DeleteLocalRef(newBooleanObj);
+                ScopedLocalRef newBooleanObj(env,
+                        env->NewObject(booleanClazz, initBoolean,
+                                s.get<FrontendStatus::Tag::isShortFrames>()));
+                env->SetObjectField(statusObj, field, newBooleanObj.get());
                 break;
             }
             case FrontendStatus::Tag::isdbtMode: {
                 jfieldID field = env->GetFieldID(clazz, "mIsdbtMode", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
-                        env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                ScopedLocalRef newIntegerObj(env,
+                        env->NewObject(intClazz, initInt,
+                                s.get<FrontendStatus::Tag::isdbtMode>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::partialReceptionFlag: {
                 jfieldID field =
-                        env->GetFieldID(clazz, "mIsdbtPartialReceptionFlag", "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                        env->GetFieldID(clazz, "mIsdbtPartialReceptionFlag",
+                                        "Ljava/lang/Integer;");
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       s.get<FrontendStatus::Tag::partialReceptionFlag>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                       s.get<FrontendStatus::Tag::partialReceptionFlag>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::streamIdList: {
                 jfieldID field = env->GetFieldID(clazz, "mStreamIds", "[I");
                 std::vector<int32_t> ids = s.get<FrontendStatus::Tag::streamIdList>();
 
-                jintArray valObj = env->NewIntArray(v.size());
-                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+                env->SetIntArrayRegion(valObj.get(), 0, v.size(),
+                                       reinterpret_cast<jint *>(&ids[0]));
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::dvbtCellIds: {
                 jfieldID field = env->GetFieldID(clazz, "mDvbtCellIds", "[I");
                 std::vector<int32_t> ids = s.get<FrontendStatus::Tag::dvbtCellIds>();
 
-                jintArray valObj = env->NewIntArray(v.size());
-                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+                ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+                env->SetIntArrayRegion(valObj.get(), 0, v.size(),
+                                       reinterpret_cast<jint *>(&ids[0]));
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::allPlpInfo: {
                 jfieldID field = env->GetFieldID(clazz, "mAllPlpInfo",
-                                                 "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;");
-                jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo");
-                jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZ)V");
+                        "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;");
+                ScopedLocalRef plpClazz(env,
+                        env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo"));
+                jmethodID initPlp = env->GetMethodID(plpClazz.get(), "<init>", "(IZ)V");
 
                 vector<FrontendScanAtsc3PlpInfo> plpInfos =
                         s.get<FrontendStatus::Tag::allPlpInfo>();
-                jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+                ScopedLocalRef valObj(env, env->NewObjectArray(plpInfos.size(), plpClazz.get(),
+                                                               nullptr));
                 for (int i = 0; i < plpInfos.size(); i++) {
-                    jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
-                                                    plpInfos[i].bLlsFlag);
-                    env->SetObjectArrayElement(valObj, i, plpObj);
-                    env->DeleteLocalRef(plpObj);
+                    ScopedLocalRef plpObj(env, env->NewObject(plpClazz.get(), initPlp,
+                                                              plpInfos[i].plpId,
+                                                              plpInfos[i].bLlsFlag));
+                    env->SetObjectArrayElement(valObj.get(), i, plpObj.get());
                 }
 
-                env->SetObjectField(statusObj, field, valObj);
-                env->DeleteLocalRef(valObj);
-                env->DeleteLocalRef(plpClazz);
+                env->SetObjectField(statusObj, field, valObj.get());
                 break;
             }
             case FrontendStatus::Tag::iptvContentUrl: {
                 jfieldID field = env->GetFieldID(clazz, "mIptvContentUrl", "Ljava/lang/String;");
                 std::string iptvContentUrl = s.get<FrontendStatus::Tag::iptvContentUrl>();
-                jstring iptvContentUrlUtf8 = env->NewStringUTF(iptvContentUrl.c_str());
-                env->SetObjectField(statusObj, field, iptvContentUrlUtf8);
-                env->DeleteLocalRef(iptvContentUrlUtf8);
+                ScopedLocalRef iptvContentUrlUtf8(env, env->NewStringUTF(iptvContentUrl.c_str()));
+                env->SetObjectField(statusObj, field, iptvContentUrlUtf8.get());
                 break;
             }
             case FrontendStatus::Tag::iptvPacketsLost: {
                 jfieldID field = env->GetFieldID(clazz, "mIptvPacketsLost", "Ljava/lang/Long;");
-                jobject newLongObj =
+                ScopedLocalRef newLongObj(env,
                         env->NewObject(longClazz, initLong,
-                                       s.get<FrontendStatus::Tag::iptvPacketsLost>());
-                env->SetObjectField(statusObj, field, newLongObj);
-                env->DeleteLocalRef(newLongObj);
+                                       s.get<FrontendStatus::Tag::iptvPacketsLost>()));
+                env->SetObjectField(statusObj, field, newLongObj.get());
                 break;
             }
             case FrontendStatus::Tag::iptvPacketsReceived: {
-                jfieldID field = env->GetFieldID(clazz, "mIptvPacketsReceived", "Ljava/lang/Long;");
-                jobject newLongObj =
+                jfieldID field = env->GetFieldID(clazz, "mIptvPacketsReceived",
+                                                 "Ljava/lang/Long;");
+                ScopedLocalRef newLongObj(env,
                         env->NewObject(longClazz, initLong,
-                                       s.get<FrontendStatus::Tag::iptvPacketsReceived>());
-                env->SetObjectField(statusObj, field, newLongObj);
-                env->DeleteLocalRef(newLongObj);
+                                       s.get<FrontendStatus::Tag::iptvPacketsReceived>()));
+                env->SetObjectField(statusObj, field, newLongObj.get());
                 break;
             }
             case FrontendStatus::Tag::iptvWorstJitterMs: {
                 jfieldID field = env->GetFieldID(clazz, "mIptvWorstJitterMs",
                                                  "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       s.get<FrontendStatus::Tag::iptvWorstJitterMs>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                       s.get<FrontendStatus::Tag::iptvWorstJitterMs>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
             case FrontendStatus::Tag::iptvAverageJitterMs: {
                 jfieldID field = env->GetFieldID(clazz, "mIptvAverageJitterMs",
                                                  "Ljava/lang/Integer;");
-                jobject newIntegerObj =
+                ScopedLocalRef newIntegerObj(env,
                         env->NewObject(intClazz, initInt,
-                                       s.get<FrontendStatus::Tag::iptvAverageJitterMs>());
-                env->SetObjectField(statusObj, field, newIntegerObj);
-                env->DeleteLocalRef(newIntegerObj);
+                                       s.get<FrontendStatus::Tag::iptvAverageJitterMs>()));
+                env->SetObjectField(statusObj, field, newIntegerObj.get());
                 break;
             }
         }
@@ -3089,21 +3041,22 @@
     vector<FrontendAtsc3PlpSettings> plps = vector<FrontendAtsc3PlpSettings>(len);
     // parse PLP settings
     for (int i = 0; i < len; i++) {
-        jobject plp = env->GetObjectArrayElement(plpSettings, i);
-        int32_t plpId = env->GetIntField(plp, env->GetFieldID(plpClazz, "mPlpId", "I"));
+        ScopedLocalRef plp(env, env->GetObjectArrayElement(plpSettings, i));
+        int32_t plpId = env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mPlpId", "I"));
         FrontendAtsc3Modulation modulation =
                 static_cast<FrontendAtsc3Modulation>(
-                        env->GetIntField(plp, env->GetFieldID(plpClazz, "mModulation", "I")));
+                        env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mModulation",
+                                                                    "I")));
         FrontendAtsc3TimeInterleaveMode interleaveMode =
                 static_cast<FrontendAtsc3TimeInterleaveMode>(
                         env->GetIntField(
-                                plp, env->GetFieldID(plpClazz, "mInterleaveMode", "I")));
+                                plp.get(), env->GetFieldID(plpClazz, "mInterleaveMode", "I")));
         FrontendAtsc3CodeRate codeRate =
                 static_cast<FrontendAtsc3CodeRate>(
-                        env->GetIntField(plp, env->GetFieldID(plpClazz, "mCodeRate", "I")));
+                        env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mCodeRate", "I")));
         FrontendAtsc3Fec fec =
                 static_cast<FrontendAtsc3Fec>(
-                        env->GetIntField(plp, env->GetFieldID(plpClazz, "mFec", "I")));
+                        env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mFec", "I")));
         FrontendAtsc3PlpSettings frontendAtsc3PlpSettings {
                 .plpId = plpId,
                 .modulation = modulation,
@@ -3112,7 +3065,6 @@
                 .fec = fec,
         };
         plps[i] = frontendAtsc3PlpSettings;
-        env->DeleteLocalRef(plp);
     }
     return plps;
 }
@@ -3457,18 +3409,17 @@
             "android/media/tv/tuner/frontend/IsdbtFrontendSettings$IsdbtLayerSettings");
     frontendIsdbtSettings.layerSettings.resize(len);
     for (int i = 0; i < len; i++) {
-        jobject layer = env->GetObjectArrayElement(layerSettings, i);
+        ScopedLocalRef layer(env, env->GetObjectArrayElement(layerSettings, i));
         frontendIsdbtSettings.layerSettings[i].modulation = static_cast<FrontendIsdbtModulation>(
-                env->GetIntField(layer, env->GetFieldID(layerClazz, "mModulation", "I")));
+                env->GetIntField(layer.get(), env->GetFieldID(layerClazz, "mModulation", "I")));
         frontendIsdbtSettings.layerSettings[i].timeInterleave =
                 static_cast<FrontendIsdbtTimeInterleaveMode>(
-                        env->GetIntField(layer,
+                        env->GetIntField(layer.get(),
                                          env->GetFieldID(layerClazz, "mTimeInterleaveMode", "I")));
         frontendIsdbtSettings.layerSettings[i].coderate = static_cast<FrontendIsdbtCoderate>(
-                env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
+                env->GetIntField(layer.get(), env->GetFieldID(layerClazz, "mCodeRate", "I")));
         frontendIsdbtSettings.layerSettings[i].numOfSegment =
-                env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
-        env->DeleteLocalRef(layer);
+                env->GetIntField(layer.get(), env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
     }
 
     frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
@@ -3498,7 +3449,8 @@
                     env->GetIntField(settings, env->GetFieldID(clazz, "mGuardInterval", "I")));
     FrontendDtmbTimeInterleaveMode interleaveMode =
             static_cast<FrontendDtmbTimeInterleaveMode>(
-                    env->GetIntField(settings, env->GetFieldID(clazz, "mTimeInterleaveMode", "I")));
+                    env->GetIntField(settings, env->GetFieldID(clazz, "mTimeInterleaveMode",
+                                                               "I")));
 
     FrontendDtmbSettings frontendDtmbSettings{
             .frequency = freq,
@@ -3515,7 +3467,8 @@
     return frontendSettings;
 }
 
-static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config, const char* className) {
+static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config,
+                                        const char* className) {
     jclass clazz = env->FindClass(className);
 
     jbyteArray jsrcIpAddress = static_cast<jbyteArray>(
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 6b1b6b1..01c998d 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -163,17 +163,19 @@
     jmethodID mRestartEventInitID;
     jfieldID mMediaEventFieldContextID;
     bool mSharedFilter;
-    void getSectionEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getMediaEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getPesEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getTsRecordEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getMmtpRecordEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getDownloadEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getIpPayloadEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getTemiEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getScramblingStatusEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getIpCidChangeEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
-    void getRestartEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getSectionEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getMediaEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getPesEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getTsRecordEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getMmtpRecordEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getDownloadEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getIpPayloadEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getTemiEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+    void getScramblingStatusEvent(const jobjectArray& arr, const int size,
+                                  const DemuxFilterEvent& event);
+    void getIpCidChangeEvent(const jobjectArray& arr, const int size,
+                             const DemuxFilterEvent& event);
+    void getRestartEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
 };
 
 struct JTuner;
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index aa5ce30..79635a0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -21,14 +21,13 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material3.TabRow
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
@@ -50,7 +49,7 @@
 
     Column {
         val coroutineScope = rememberCoroutineScope()
-        val pagerState by rememberPageStateFixed()
+        val pagerState = rememberPageStateFixed()
 
         TabRow(
             selectedTabIndex = pagerState.currentPage,
@@ -89,17 +88,30 @@
  */
 @Composable
 @OptIn(ExperimentalFoundationApi::class)
-private fun rememberPageStateFixed(): State<PagerState> {
+private fun rememberPageStateFixed(): PagerState {
     var currentPage by rememberSaveable { mutableStateOf(0) }
-    val pagerStateHolder = remember { mutableStateOf(PagerState(currentPage)) }
-    LaunchedEffect(LocalConfiguration.current.orientation) {
+    var targetPage by rememberSaveable { mutableStateOf(-1) }
+    val pagerState = rememberPagerState()
+    val configuration = LocalConfiguration.current
+    var lastScreenWidthDp by rememberSaveable { mutableStateOf(-1) }
+    val screenWidthDp = configuration.screenWidthDp
+    LaunchedEffect(screenWidthDp) {
         // Reset pager state to fix an issue after configuration change.
-        // When we declare android:configChanges="orientation" in the manifest, the pager state is
-        // in a weird state after configuration change.
-        pagerStateHolder.value = PagerState(currentPage)
+        // When we declare android:configChanges in the manifest, the pager state is in a weird
+        // state after configuration change.
+        targetPage = currentPage
+        lastScreenWidthDp = screenWidthDp
+    }
+    LaunchedEffect(targetPage) {
+        if (targetPage != -1) {
+            pagerState.scrollToPage(targetPage)
+            targetPage = -1
+        }
     }
     SideEffect {
-        currentPage = pagerStateHolder.value.currentPage
+        if (lastScreenWidthDp == screenWidthDp) {
+            currentPage = pagerState.currentPage
+        }
     }
-    return pagerStateHolder
+    return pagerState
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 4d6dd4b..f5bacb6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -250,6 +250,7 @@
                 }
             }
             cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
+            mDeviceManager.onActiveDeviceChanged(cachedDevice);
         }
         for (BluetoothCallback callback : mCallbacks) {
             callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 67e3e03..d55144e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -328,6 +328,13 @@
         return false;
     }
 
+    /** Handles when the device been set as active/inactive. */
+    public synchronized void onActiveDeviceChanged(CachedBluetoothDevice cachedBluetoothDevice) {
+        if (cachedBluetoothDevice.isHearingAidDevice()) {
+            mHearingAidDeviceManager.onActiveDeviceChanged(cachedBluetoothDevice);
+        }
+    }
+
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
         device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 4354e0c..e5e5782 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -224,15 +224,9 @@
                         // It is necessary to do remove and add for updating the mapping on
                         // preference and device
                         mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
-                        // Only need to set first device of a set. AudioDeviceInfo for
-                        // GET_DEVICES_OUTPUTS will not change device.
-                        setAudioRoutingConfig(cachedDevice);
                     }
                     return true;
                 }
-                // Only need to set first device of a set. AudioDeviceInfo for GET_DEVICES_OUTPUTS
-                // will not change device.
-                setAudioRoutingConfig(cachedDevice);
                 break;
             case BluetoothProfile.STATE_DISCONNECTED:
                 mainDevice = findMainDevice(cachedDevice);
@@ -258,13 +252,20 @@
 
                     return true;
                 }
-                // Only need to clear when last device of a set get disconnected
-                clearAudioRoutingConfig();
                 break;
         }
         return false;
     }
 
+    void onActiveDeviceChanged(CachedBluetoothDevice device) {
+        if (device.isActiveDevice(BluetoothProfile.HEARING_AID) || device.isActiveDevice(
+                BluetoothProfile.LE_AUDIO)) {
+            setAudioRoutingConfig(device);
+        } else {
+            clearAudioRoutingConfig();
+        }
+    }
+
     private void setAudioRoutingConfig(CachedBluetoothDevice device) {
         AudioDeviceAttributes hearingDeviceAttributes =
                 mRoutingHelper.getMatchedHearingDeviceAttributes(device);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 3361a66..8c316d1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -46,6 +46,7 @@
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
@@ -396,6 +397,21 @@
     }
 
     @Test
+    public void dispatchActiveDeviceChanged_callExpectedOnActiveDeviceChanged() {
+        mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+
+        mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice1,
+                BluetoothProfile.HEARING_AID);
+
+        verify(mCachedDeviceManager).onActiveDeviceChanged(mCachedDevice1);
+        verify(mBluetoothCallback).onActiveDeviceChanged(mCachedDevice1,
+                BluetoothProfile.HEARING_AID);
+    }
+
+    @Test
     public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 4b3820e..7e7c76e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -17,7 +17,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -604,4 +606,20 @@
         verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
         verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
     }
+
+    @Test
+    public void onActiveDeviceChanged_validHiSyncId_callExpectedFunction() {
+        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
+                mCachedDeviceManager.mCachedDevices));
+        doNothing().when(mHearingAidDeviceManager).onActiveDeviceChanged(any());
+        mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        cachedDevice1.setHearingAidInfo(
+                new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
+
+        mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1);
+
+        verify(mHearingAidDeviceManager).onActiveDeviceChanged(cachedDevice1);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index a839136..0d5de88 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -478,37 +478,24 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_connected_callSetStrategies() {
+    public void onActiveDeviceChanged_connected_callSetStrategies() {
         when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
                 mHearingDeviceAttribute);
+        when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
 
-        mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
-                BluetoothProfile.STATE_CONNECTED);
+        mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
 
         verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
                 eq(List.of(mAudioStrategy)), any(AudioDeviceAttributes.class), anyInt());
     }
 
     @Test
-    public void onProfileConnectionStateChanged_disconnected_callSetStrategiesWithAutoValue() {
+    public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() {
         when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
                 mHearingDeviceAttribute);
+        when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
 
-        mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
-                BluetoothProfile.STATE_DISCONNECTED);
-
-        verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
-                eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
-                eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
-    }
-    @Test
-    public void onProfileConnectionStateChanged_unpairing_callSetStrategiesWithAutoValue() {
-        when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
-                mHearingDeviceAttribute);
-
-        when(mCachedDevice1.getUnpairing()).thenReturn(true);
-        mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
-                BluetoothProfile.STATE_DISCONNECTED);
+        mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
 
         verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
                 eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index c6c3019..6f6d0f9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -62,7 +62,7 @@
     override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
-                    UserAction.Back to SceneModel(SceneKey.LockScreen),
+                    UserAction.Back to SceneModel(SceneKey.Lockscreen),
                 )
             )
             .asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
similarity index 91%
rename from packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index ad33eb5..ab7bc26 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.ui.viewmodel.LockScreenSceneViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -48,13 +48,13 @@
 
 /** The lock screen scene shows when the device is locked. */
 @SysUISingleton
-class LockScreenScene
+class LockscreenScene
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val viewModel: LockScreenSceneViewModel,
+    private val viewModel: LockscreenSceneViewModel,
 ) : ComposableScene {
-    override val key = SceneKey.LockScreen
+    override val key = SceneKey.Lockscreen
 
     override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         viewModel.upDestinationSceneKey
@@ -69,7 +69,7 @@
     override fun Content(
         modifier: Modifier,
     ) {
-        LockScreenScene(
+        LockscreenScene(
             viewModel = viewModel,
             modifier = modifier,
         )
@@ -86,8 +86,8 @@
 }
 
 @Composable
-private fun LockScreenScene(
-    viewModel: LockScreenSceneViewModel,
+private fun LockscreenScene(
+    viewModel: LockscreenSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/280879610): implement the real UI.
@@ -99,7 +99,7 @@
             horizontalAlignment = Alignment.CenterHorizontally,
             modifier = Modifier.align(Alignment.Center)
         ) {
-            Text("Lock screen", style = MaterialTheme.typography.headlineMedium)
+            Text("Lockscreen", style = MaterialTheme.typography.headlineMedium)
             Row(
                 horizontalArrangement = Arrangement.spacedBy(8.dp),
             ) {
diff --git a/packages/SystemUI/docs/device-entry/keyguard.md b/packages/SystemUI/docs/device-entry/keyguard.md
index 8634c95..1898b97 100644
--- a/packages/SystemUI/docs/device-entry/keyguard.md
+++ b/packages/SystemUI/docs/device-entry/keyguard.md
@@ -20,6 +20,10 @@
 
 An indication to power off the device most likely comes from one of two signals: the user presses the power button or the screen timeout has passed. This may [lock the device](#How-the-device-locks)
 
+#### Long-pressing on keyguard
+
+OEMs may choose to enable a long-press action that displays a button at the bottom of lockscreen. This button links to lockscreen customization. This can be achieved by overriding the `long_press_keyguard_customize_lockscreen_enabled` resource in `packages/SystemUI/res/values/config.xml`.
+
 #### On Lockscreen
 
 #### On Lockscreen, occluded by an activity
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index d662649..afcf846 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -17,7 +17,9 @@
 By default, AOSP ships with a "bottom right" and a "bottom left" slot, each with a slot capacity of `1`, allowing only one Quick Affordance on each side of the lock screen.
 
 ### Customizing Slots
-OEMs may choose to override the IDs and number of slots and/or override the default capacities. This can be achieved by overridding the `config_keyguardQuickAffordanceSlots` resource in `packages/SystemUI/res/values/config.xml`.
+OEMs may choose to enable customization of slots. An entry point in settings will appear when overriding the `custom_lockscreen_shortcuts_enabled` resource in `packages/SystemUI/res/values/config.xml`.
+
+OEMs may also choose to override the IDs and number of slots and/or override the default capacities. This can be achieved by overridding the `config_keyguardQuickAffordanceSlots` resource in `packages/SystemUI/res/values/config.xml`.
 
 ### Default Quick Affordances
 OEMs may also choose to predefine default Quick Affordances for each slot. To achieve this, a developer may override the `config_keyguardQuickAffordanceDefaults` resource in `packages/SystemUI/res/values/config.xml`. Note that defaults only work until the user of the device selects a different quick affordance for that slot, even if they select the "None" option.
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 8bb7877..371670c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -39,7 +39,8 @@
         android:ellipsize="marquee"
         android:visibility="gone"
         android:gravity="center"
-        androidprv:allCaps="@bool/kg_use_all_caps" />
+        androidprv:allCaps="@bool/kg_use_all_caps"
+        androidprv:debugLocation="Emergency" />
 
     <com.android.keyguard.EmergencyButton
         android:id="@+id/emergency_call_button"
diff --git a/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
index 3b67ddd..bab604d 100644
--- a/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
@@ -18,9 +18,7 @@
 -->
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-  <solid android:color="?androidprv:attr/colorSurface"/>
   <size
       android:width="@dimen/dream_overlay_bottom_affordance_height"
       android:height="@dimen/dream_overlay_bottom_affordance_width"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index 0cd0623..e8a48c7 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -21,8 +21,6 @@
     android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
     android:layout_gravity="bottom|start"
     android:padding="@dimen/dream_overlay_bottom_affordance_padding"
-    android:background="@drawable/dream_overlay_bottom_affordance_bg"
     android:scaleType="fitCenter"
     android:tint="?android:attr/textColorPrimary"
-    android:src="@drawable/controls_icon"
     android:contentDescription="@string/quick_controls_title" />
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index e9acf3f..a3a7135 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,55 +59,44 @@
 
     </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:layout_gravity="bottom"
-        android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
+    <com.android.systemui.animation.view.LaunchableImageView
+        android:id="@+id/start_button"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="start|bottom"
+        android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
-        android:gravity="bottom"
-        >
+        android:scaleType="fitCenter"
+        android:padding="@dimen/keyguard_affordance_fixed_padding"
+        android:tint="?android:attr/textColorPrimary"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+        android:visibility="invisible" />
 
-        <com.android.systemui.animation.view.LaunchableImageView
-            android:id="@+id/start_button"
-            android:layout_height="@dimen/keyguard_affordance_fixed_height"
-            android:layout_width="@dimen/keyguard_affordance_fixed_width"
-            android:scaleType="fitCenter"
-            android:padding="@dimen/keyguard_affordance_fixed_padding"
-            android:tint="?android:attr/textColorPrimary"
-            android:background="@drawable/keyguard_bottom_affordance_bg"
-            android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
-            android:visibility="invisible" />
+    <com.android.systemui.animation.view.LaunchableImageView
+        android:id="@+id/end_button"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="end|bottom"
+        android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:scaleType="fitCenter"
+        android:padding="@dimen/keyguard_affordance_fixed_padding"
+        android:tint="?android:attr/textColorPrimary"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+        android:visibility="invisible" />
 
-        <FrameLayout
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:paddingHorizontal="24dp"
-            >
-            <include
-                android:id="@+id/keyguard_settings_button"
-                layout="@layout/keyguard_settings_popup_menu"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:visibility="gone"
-                />
-        </FrameLayout>
-
-        <com.android.systemui.animation.view.LaunchableImageView
-            android:id="@+id/end_button"
-            android:layout_height="@dimen/keyguard_affordance_fixed_height"
-            android:layout_width="@dimen/keyguard_affordance_fixed_width"
-            android:scaleType="fitCenter"
-            android:padding="@dimen/keyguard_affordance_fixed_padding"
-            android:tint="?android:attr/textColorPrimary"
-            android:background="@drawable/keyguard_bottom_affordance_bg"
-            android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
-            android:visibility="invisible" />
-
-    </LinearLayout>
+    <include
+        android:id="@+id/keyguard_settings_button"
+        layout="@layout/keyguard_settings_popup_menu"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
+        android:visibility="gone"
+        />
 
     <FrameLayout
         android:id="@+id/overlay_container"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 8b85940..64c4eff 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -78,6 +78,7 @@
         android:textColor="?attr/wallpaperTextColorSecondary"
         android:singleLine="true"
         systemui:showMissingSim="true"
-        systemui:showAirplaneMode="true" />
+        systemui:showAirplaneMode="true"
+        systemui:debugLocation="Keyguard" />
 
 </com.android.systemui.statusbar.phone.KeyguardStatusBarView>
diff --git a/packages/SystemUI/res/raw/sfps_pulse.json b/packages/SystemUI/res/raw/sfps_pulse.json
index 9fe4eb6..2a72dfb 100644
--- a/packages/SystemUI/res/raw/sfps_pulse.json
+++ b/packages/SystemUI/res/raw/sfps_pulse.json
@@ -1 +1 @@
-{"v":"5.7.13","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"sfps_pulse_motion_04","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.617,"y":0.539},"o":{"x":0.477,"y":0},"t":0,"s":[28,40,0],"to":[0.365,0,0],"ti":[-1.009,0,0]},{"i":{"x":0.436,"y":1},"o":{"x":0.17,"y":0.547},"t":10,"s":[30.576,40,0],"to":[1.064,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[75]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[75]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[75]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[75]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[75]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[75]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[75]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[75]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[75]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[75]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[75]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
+{"v":"5.7.14","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"sfps_pulse_motion_04","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.617,"y":0.539},"o":{"x":0.477,"y":0},"t":0,"s":[28,40,0],"to":[0.365,0,0],"ti":[-1.009,0,0]},{"i":{"x":0.436,"y":1},"o":{"x":0.17,"y":0.547},"t":10,"s":[30.576,40,0],"to":[1.064,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[75]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[75]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[75]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[75]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[75]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[75]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[75]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[75]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[75]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[75]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[75]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8d8fdf0..bd86e51 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -146,6 +146,7 @@
         <attr name="allCaps" format="boolean" />
         <attr name="showMissingSim" format="boolean" />
         <attr name="showAirplaneMode" format="boolean" />
+        <attr name="debugLocation" format="string" />
     </declare-styleable>
 
     <declare-styleable name="IlluminationDrawable">
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b4e1b66..c3651cf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -44,6 +44,12 @@
     <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
     <integer name="navigation_bar_deadzone_orientation">0</integer>
 
+    <!-- Whether or not lockscreen shortcuts can be customized -->
+    <bool name="custom_lockscreen_shortcuts_enabled">false</bool>
+
+    <!-- Whether or not long-pressing on keyguard will display to customize lockscreen -->
+    <bool name="long_press_keyguard_customize_lockscreen_enabled">false</bool>
+
     <bool name="config_dead_zone_flash">false</bool>
 
     <!-- Whether to enable dimming navigation buttons when wallpaper is not visible, should be
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7187de8..d5806ec 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -45,6 +45,9 @@
     <dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
     <!-- The threshold to progress back animation for edge swipe -->
     <dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
+    <!-- This value is used to calculate the target if the screen is wider than the
+        navigation_edge_action_progress_threshold. See BackAnimation#setSwipeThresholds -->
+    <item name="back_progress_non_linear_factor" format="float" type="dimen">0.2</item>
     <!-- The minimum display position of the arrow on the screen -->
     <dimen name="navigation_edge_arrow_min_y">64dp</dimen>
     <!-- The amount by which the arrow is shifted to avoid the finger-->
@@ -1643,6 +1646,19 @@
     <dimen name="dream_overlay_bottom_affordance_height">64dp</dimen>
     <dimen name="dream_overlay_bottom_affordance_width">64dp</dimen>
     <dimen name="dream_overlay_bottom_affordance_radius">32dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_key_text_shadow_dx">0.5dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_key_text_shadow_dy">0.5dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_key_text_shadow_radius">1dp</dimen>
+    <item name="dream_overlay_bottom_affordance_key_shadow_alpha" format="float" type="dimen">
+        0.35
+    </item>
+    <dimen name="dream_overlay_bottom_affordance_ambient_text_shadow_dx">0.5dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_ambient_text_shadow_dy">0.5dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_ambient_text_shadow_radius">2dp</dimen>
+    <item name="dream_overlay_bottom_affordance_ambient_shadow_alpha" format="float" type="dimen">
+        0.4
+    </item>
+    <dimen name="dream_overlay_bottom_affordance_inset">1dp</dimen>
     <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
     <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
index 19d0a3d..6b9274c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.shared.shadow
 
+import android.content.res.ColorStateList
 import android.graphics.BlendMode
 import android.graphics.Canvas
 import android.graphics.Color
@@ -106,6 +107,14 @@
         mIconDrawable.draw(canvas)
     }
 
+    override fun getIntrinsicHeight(): Int {
+        return mCanvasSize
+    }
+
+    override fun getIntrinsicWidth(): Int {
+        return mCanvasSize
+    }
+
     override fun getOpacity(): Int {
         return PixelFormat.TRANSPARENT
     }
@@ -121,4 +130,8 @@
     override fun setTint(color: Int) {
         mIconDrawable.setTint(color)
     }
+
+    override fun setTintList(tint: ColorStateList?) {
+        mIconDrawable.setTintList(tint)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index e4f6e131..87a9b0f 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -33,6 +33,8 @@
 
     private final boolean mShowAirplaneMode;
 
+    private final String mDebugLocation;
+
     public CarrierText(Context context) {
         this(context, null);
     }
@@ -46,6 +48,7 @@
             useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
             mShowAirplaneMode = a.getBoolean(R.styleable.CarrierText_showAirplaneMode, false);
             mShowMissingSim = a.getBoolean(R.styleable.CarrierText_showMissingSim, false);
+            mDebugLocation = a.getString(R.styleable.CarrierText_debugLocation);
         } finally {
             a.recycle();
         }
@@ -70,6 +73,10 @@
         return mShowMissingSim;
     }
 
+    public String getDebugLocation() {
+        return mDebugLocation;
+    }
+
     private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
         private final Locale mLocale;
         private final boolean mAllCaps;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 997c527..33f9ecd 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -53,6 +53,7 @@
         mCarrierTextManager = carrierTextManagerBuilder
                 .setShowAirplaneMode(mView.getShowAirplaneMode())
                 .setShowMissingSim(mView.getShowMissingSim())
+                .setDebugLocationString(mView.getDebugLocation())
                 .build();
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 1f75e81..a724514 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -651,6 +651,7 @@
         private final CarrierTextManagerLogger mLogger;
         private boolean mShowAirplaneMode;
         private boolean mShowMissingSim;
+        private String mDebugLocation;
 
         @Inject
         public Builder(
@@ -689,14 +690,25 @@
             return this;
         }
 
+        /**
+         * To help disambiguate logs, set a location to be used in the LogBuffer calls, e.g.:
+         * "keyguard" or "keyguard emergency status bar"
+         */
+        public Builder setDebugLocationString(String debugLocationString) {
+            mDebugLocation = debugLocationString;
+            return this;
+        }
+
         /** Create a CarrierTextManager. */
         public CarrierTextManager build() {
+            mLogger.setLocation(mDebugLocation);
             return new CarrierTextManager(
                     mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
                     mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
                     mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger);
         }
     }
+
     /**
      * Data structure for passing information to CarrierTextController subscribers
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 4bf7be6..c5b14e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -108,6 +108,7 @@
                 }
                 updateFontSizes()
                 updateTimeListeners()
+                cachedWeatherData?.let { value.events.onWeatherDataChanged(it) }
                 value.smallClock.view.addOnAttachStateChangeListener(
                     object : OnAttachStateChangeListener {
                         override fun onViewAttachedToWindow(p0: View?) {
@@ -239,6 +240,7 @@
     var largeTimeListener: TimeListener? = null
     val shouldTimeListenerRun: Boolean
         get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
+    private var cachedWeatherData: WeatherData? = null
 
     private var smallClockIsDark = true
     private var largeClockIsDark = true
@@ -305,6 +307,7 @@
             }
 
             override fun onWeatherDataChanged(data: WeatherData) {
+                cachedWeatherData = data
                 clock?.run { events.onWeatherDataChanged(data) }
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5cc0547..4b02171 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -599,7 +599,6 @@
      */
     public void startAppearAnimation(SecurityMode securityMode) {
         setTranslationY(0f);
-        setAlpha(1f);
         updateChildren(0 /* translationY */, 1f /* alpha */);
         mViewMode.startAppearAnimation(securityMode);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5b1edc7..b5e5420 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -674,7 +674,6 @@
 
     public void startAppearAnimation() {
         if (mCurrentSecurityMode != SecurityMode.None) {
-            setAlpha(1f);
             mView.startAppearAnimation(mCurrentSecurityMode);
             getCurrentSecurityController(controller -> controller.startAppearAnimation());
         }
@@ -1112,7 +1111,7 @@
      */
     public void setExpansion(float fraction) {
         float scaledFraction = BouncerPanelExpansionCalculator.showBouncerProgress(fraction);
-        mView.setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
+        setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
         mView.setTranslationY(scaledFraction * mTranslationY);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index 4001a4e..1978715 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -18,16 +18,20 @@
 
 import androidx.annotation.IntDef
 import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.CarrierTextManagerLog
 import javax.inject.Inject
 
 /** Logger adapter for [CarrierTextManager] to add detailed messages in a [LogBuffer] */
-@SysUISingleton
 class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val buffer: LogBuffer) {
     /**
+     * To help disambiguate carrier text manager instances, set a location string here which will
+     * propagate to [logUpdate] and [logUpdateCarrierTextForReason]
+     */
+    var location: String? = null
+
+    /**
      * This method and the methods below trace the execution of CarrierTextManager.updateCarrierText
      */
     fun logUpdate(numSubs: Int) {
@@ -35,7 +39,7 @@
             TAG,
             LogLevel.VERBOSE,
             { int1 = numSubs },
-            { "updateCarrierText: numSubs=$int1" },
+            { "updateCarrierText: location=${location ?: "(unknown)"} numSubs=$int1" },
         )
     }
 
@@ -99,7 +103,10 @@
             TAG,
             LogLevel.DEBUG,
             { int1 = reason },
-            { "refreshing carrier info for reason: ${reason.reasonMessage()}" }
+            {
+                "refreshing carrier info for reason: ${reason.reasonMessage()}" +
+                    " location=${location ?: "(unknown)"}"
+            }
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index cd195f6..9d9a87d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -33,7 +33,7 @@
      * A device that is not yet unlocked requires unlocking by completing an authentication
      * challenge according to the current authentication method.
      *
-     * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
+     * Note that this state has no real bearing on whether the lockscreen is showing or dismissed.
      */
     val isUnlocked: StateFlow<Boolean>
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 9007279..0782537 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -20,6 +20,7 @@
 import android.app.ActivityTaskManager
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Color
 import android.graphics.PixelFormat
 import android.graphics.PorterDuff
 import android.graphics.PorterDuffColorFilter
@@ -459,7 +460,12 @@
             addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
                 PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
             }
-        } else if (isDarkMode(context)) {
+        } else {
+            if (!isDarkMode(context)) {
+                addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+                    PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+                }
+            }
             for (key in listOf(".blue600", ".blue400")) {
                 addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
                     PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 57ce580..8264fed 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -73,7 +73,7 @@
                             is AuthenticationMethodModel.Swipe ->
                                 sceneInteractor.setCurrentScene(
                                     containerName,
-                                    SceneModel(SceneKey.LockScreen),
+                                    SceneModel(SceneKey.Lockscreen),
                                 )
                             else -> Unit
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index f973aee..4d99282 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
@@ -39,6 +40,7 @@
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.controls.ui.ControlsActivity;
 import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -56,17 +58,20 @@
  * devices at home like lights and thermostats).
  */
 public class DreamHomeControlsComplication implements Complication {
+    private final Resources mResources;
     private final DreamHomeControlsComplicationComponent.Factory mComponentFactory;
 
     @Inject
     public DreamHomeControlsComplication(
+            @Main Resources resources,
             DreamHomeControlsComplicationComponent.Factory componentFactory) {
+        mResources = resources;
         mComponentFactory = componentFactory;
     }
 
     @Override
     public ViewHolder createView(ComplicationViewModel model) {
-        return mComponentFactory.create().getViewHolder();
+        return mComponentFactory.create(mResources).getViewHolder();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java
index ef18d66..2b5aa7c 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -18,12 +18,19 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.widget.ImageView;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.complication.DreamHomeControlsComplication;
+import com.android.systemui.shared.shadow.DoubleShadowIconDrawable;
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper;
 
+import dagger.BindsInstance;
 import dagger.Module;
 import dagger.Provides;
 import dagger.Subcomponent;
@@ -59,7 +66,7 @@
      */
     @Subcomponent.Factory
     interface Factory {
-        DreamHomeControlsComplicationComponent create();
+        DreamHomeControlsComplicationComponent create(@BindsInstance Resources resources);
     }
 
     /**
@@ -68,6 +75,7 @@
     @Module
     interface DreamHomeControlsModule {
         String DREAM_HOME_CONTROLS_CHIP_VIEW = "dream_home_controls_chip_view";
+        String DREAM_HOME_CONTROLS_BACKGROUND_DRAWABLE = "dream_home_controls_background_drawable";
 
         /**
          * Provides the dream home controls chip view.
@@ -75,9 +83,56 @@
         @Provides
         @DreamHomeControlsComplicationScope
         @Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
-        static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
-            return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
-                    null, false);
+        static ImageView provideHomeControlsChipView(
+                LayoutInflater layoutInflater,
+                @Named(DREAM_HOME_CONTROLS_BACKGROUND_DRAWABLE) Drawable backgroundDrawable) {
+            final ImageView chip =
+                    (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+                            null, false);
+            chip.setBackground(backgroundDrawable);
+
+            return chip;
+        }
+
+        @Provides
+        @DreamHomeControlsComplicationScope
+        @Named(DREAM_HOME_CONTROLS_BACKGROUND_DRAWABLE)
+        static Drawable providesHomeControlsBackground(Context context, Resources resources) {
+            final Drawable background = new DoubleShadowIconDrawable(createShadowInfo(
+                            resources,
+                            R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius,
+                            R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx,
+                            R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dy,
+                            R.dimen.dream_overlay_bottom_affordance_key_shadow_alpha
+                    ),
+                    createShadowInfo(
+                            resources,
+                            R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_radius,
+                            R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dx,
+                            R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dy,
+                            R.dimen.dream_overlay_bottom_affordance_ambient_shadow_alpha
+                    ),
+                    resources.getDrawable(R.drawable.dream_overlay_bottom_affordance_bg),
+                    resources.getDimensionPixelOffset(
+                            R.dimen.dream_overlay_bottom_affordance_width),
+                    resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset)
+            );
+
+            background.setTintList(
+                    Utils.getColorAttr(context, com.android.internal.R.attr.colorSurface));
+
+            return background;
+        }
+
+        private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources,
+                int blurId, int offsetXId, int offsetYId, int alphaId) {
+
+            return new DoubleShadowTextHelper.ShadowInfo(
+                    resources.getDimension(blurId),
+                    resources.getDimension(offsetXId),
+                    resources.getDimension(offsetYId),
+                    resources.getFloat(alphaId)
+            );
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 340ed2e..05df31b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -338,8 +338,7 @@
 
     // TODO(b/280426085): Tracking Bug
     @JvmField
-    val NEW_BLUETOOTH_REPOSITORY =
-        unreleasedFlag(612, "new_bluetooth_repository", teamfood = true)
+    val NEW_BLUETOOTH_REPOSITORY = unreleasedFlag(612, "new_bluetooth_repository")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -562,7 +561,7 @@
 
     // TODO(b/270987164): Tracking Bug
     @JvmField
-    val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
+    val TRACKPAD_GESTURE_FEATURES = unreleasedFlag(1205, "trackpad_gesture_features", teamfood = true)
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
@@ -613,7 +612,8 @@
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
     // TODO(b/278714186) Tracking Bug
-    @JvmField val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout")
+    @JvmField val CLIPBOARD_IMAGE_TIMEOUT =
+            unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
     // TODO(b/279405451): Tracking Bug
     @JvmField
     val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index bd6dfe3..f96f337 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -258,12 +258,10 @@
      */
     private var surfaceBehindAlpha = 1f
 
-    private var wallpaperAlpha = 1f
-
     @VisibleForTesting
     var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
 
-    var wallpaperAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+    var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
 
     /**
      * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -330,6 +328,7 @@
                     if (surfaceBehindAlpha == 0f) {
                         Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
                         surfaceBehindRemoteAnimationTargets = null
+                        wallpaperTargets = null
                         keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
                             false /* cancelled */)
                     } else {
@@ -340,23 +339,17 @@
             })
         }
 
-        with(wallpaperAlphaAnimator) {
+        with(wallpaperCannedUnlockAnimator) {
             duration = LAUNCHER_ICONS_ANIMATION_DURATION_MS
             interpolator = Interpolators.ALPHA_OUT
             addUpdateListener { valueAnimator: ValueAnimator ->
-                wallpaperAlpha = valueAnimator.animatedValue as Float
-                setWallpaperAppearAmount(wallpaperAlpha)
+                setWallpaperAppearAmount(valueAnimator.animatedValue as Float)
             }
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
-                    Log.d(TAG, "wallpaperAlphaAnimator#onAnimationEnd, animation ended ")
-                    if (wallpaperAlpha == 1f) {
-                        wallpaperTargets = null
-                        keyguardViewMediator.get().finishExitRemoteAnimation()
-                    } else {
-                        Log.d(TAG, "wallpaperAlphaAnimator#onAnimationEnd, " +
-                                "animation was cancelled: skipping finishAnimation()")
-                    }
+                    Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+                    keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+                        false /* cancelled */)
                 }
             })
         }
@@ -387,11 +380,6 @@
             context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
     }
 
-    fun isAnyKeyguyardAnimatorPlaying(): Boolean {
-        return surfaceBehindAlphaAnimator.isStarted ||
-                wallpaperAlphaAnimator.isStarted || surfaceBehindEntryAnimator.isStarted
-    }
-
     /**
      * Add a listener to be notified of various stages of the unlock animation.
      */
@@ -536,8 +524,6 @@
         wallpaperTargets = wallpapers
         surfaceBehindRemoteAnimationStartTime = startTime
 
-        fadeInWallpaper()
-
         // If we specifically requested that the surface behind be made visible (vs. it being made
         // visible because we're unlocking), then we're in the middle of a swipe-to-unlock touch
         // gesture and the surface behind the keyguard should be made visible so that we can animate
@@ -599,7 +585,9 @@
         // Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
         // Check it here in case there is no more change to the dismiss amount after the last change
         // that starts the keyguard animation. @see #updateKeyguardViewMediatorIfThresholdsReached()
-        finishKeyguardExitRemoteAnimationIfReachThreshold()
+        if (!playingCannedUnlockAnimation) {
+            finishKeyguardExitRemoteAnimationIfReachThreshold()
+        }
     }
 
     /**
@@ -650,7 +638,7 @@
      * transition if possible.
      */
     private fun unlockToLauncherWithInWindowAnimations() {
-        setSurfaceBehindAppearAmount(1f)
+        setSurfaceBehindAppearAmount(1f, wallpapers = false)
 
         try {
             // Begin the animation, waiting for the shade to animate out.
@@ -676,19 +664,8 @@
         // visible, hide our smartspace.
         lockscreenSmartspace?.visibility = View.INVISIBLE
 
-        // As soon as the shade has animated out of the way, finish the keyguard exit animation. The
-        // in-window animations in the Launcher window will end on their own.
-        handler.postDelayed({
-            if (keyguardViewMediator.get().isShowingAndNotOccluded &&
-                !keyguardStateController.isKeyguardGoingAway) {
-                    Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
-                            "showing and not going away.")
-                return@postDelayed
-            }
-
-            keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                false /* cancelled */)
-        }, CANNED_UNLOCK_START_DELAY)
+        // Start an animation for the wallpaper, which will finish keyguard exit when it completes.
+        fadeInWallpaper()
     }
 
     /**
@@ -809,7 +786,16 @@
      * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is
      * cancelled).
      */
-    fun setSurfaceBehindAppearAmount(amount: Float) {
+    fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
+        val animationAlpha = when {
+            // If we're snapping the keyguard back, immediately begin fading it out.
+            keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+            // If the screen has turned back off, the unlock animation is going to be cancelled,
+            // so set the surface alpha to 0f so it's no longer visible.
+            !powerManager.isInteractive -> 0f
+            else -> surfaceBehindAlpha
+        }
+
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
             val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
@@ -839,16 +825,6 @@
                     surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
             )
 
-
-            val animationAlpha = when {
-                // If we're snapping the keyguard back, immediately begin fading it out.
-                keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
-                // If the screen has turned back off, the unlock animation is going to be cancelled,
-                // so set the surface alpha to 0f so it's no longer visible.
-                !powerManager.isInteractive -> 0f
-                else -> surfaceBehindAlpha
-            }
-
             // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
             // unable to draw
             val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
@@ -863,28 +839,27 @@
             } else {
                 applyParamsToSurface(
                         SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                                surfaceBehindRemoteAnimationTarget.leash)
-                                .withMatrix(surfaceBehindMatrix)
-                                .withCornerRadius(roundedCornerRadius)
-                                .withAlpha(animationAlpha)
-                                .build()
+                            surfaceBehindRemoteAnimationTarget.leash)
+                            .withMatrix(surfaceBehindMatrix)
+                            .withCornerRadius(roundedCornerRadius)
+                            .withAlpha(animationAlpha)
+                            .build()
                 )
             }
         }
+
+        if (wallpapers) {
+            setWallpaperAppearAmount(amount)
+        }
     }
 
-    /**
-     * Modify the opacity of a wallpaper window.
-     */
     fun setWallpaperAppearAmount(amount: Float) {
-        wallpaperTargets?.forEach { wallpaper ->
-            val animationAlpha = when {
-                // If the screen has turned back off, the unlock animation is going to be cancelled,
-                // so set the surface alpha to 0f so it's no longer visible.
-                !powerManager.isInteractive -> 0f
-                else -> amount
-            }
+        val animationAlpha = when {
+            !powerManager.isInteractive -> 0f
+            else -> amount
+        }
 
+        wallpaperTargets?.forEach { wallpaper ->
             // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
             // unable to draw
             val sc: SurfaceControl? = wallpaper.leash
@@ -923,6 +898,7 @@
         setSurfaceBehindAppearAmount(1f)
         surfaceBehindAlphaAnimator.cancel()
         surfaceBehindEntryAnimator.cancel()
+        wallpaperCannedUnlockAnimator.cancel()
         try {
             launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
         } catch (e: RemoteException) {
@@ -931,6 +907,7 @@
 
         // That target is no longer valid since the animation finished, null it out.
         surfaceBehindRemoteAnimationTargets = null
+        wallpaperTargets = null
 
         playingCannedUnlockAnimation = false
         willUnlockWithInWindowLauncherAnimations = false
@@ -972,8 +949,8 @@
 
     private fun fadeInWallpaper() {
         Log.d(TAG, "fadeInWallpaper")
-        wallpaperAlphaAnimator.cancel()
-        wallpaperAlphaAnimator.start()
+        wallpaperCannedUnlockAnimator.cancel()
+        wallpaperCannedUnlockAnimator.start()
     }
 
     private fun fadeOutSurfaceBehind() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 51af8fb..45e4623 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3009,19 +3009,6 @@
         mSurfaceBehindRemoteAnimationRequested = false;
         mSurfaceBehindRemoteAnimationRunning = false;
         mKeyguardStateController.notifyKeyguardGoingAway(false);
-        finishExitRemoteAnimation();
-    }
-
-    void finishExitRemoteAnimation() {
-        if (mKeyguardUnlockAnimationControllerLazy.get().isAnyKeyguyardAnimatorPlaying()
-                || mKeyguardStateController.isDismissingFromSwipe()) {
-            // If the animation is ongoing, or we are not done with the swipe gesture,
-            // it's too early to terminate the animation
-            Log.d(TAG, "finishAnimation not executing now because "
-                    + "not all animations have finished");
-            return;
-        }
-        Log.d(TAG, "finishAnimation executing");
 
         if (mSurfaceBehindRemoteAnimationFinishedCallback != null) {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
index ea6700e..ca430da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -17,12 +17,14 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.view.accessibility.AccessibilityManager
 import androidx.annotation.VisibleForTesting
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -55,6 +57,7 @@
 class KeyguardLongPressInteractor
 @Inject
 constructor(
+    @Application private val appContext: Context,
     @Application private val scope: CoroutineScope,
     transitionInteractor: KeyguardTransitionInteractor,
     repository: KeyguardRepository,
@@ -169,7 +172,8 @@
 
     private fun isFeatureEnabled(): Boolean {
         return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) &&
-            featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI)
+            featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) &&
+            appContext.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled)
     }
 
     /** Updates application state to ask to show the menu. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 8e65c4d..2275337 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -19,12 +19,15 @@
 
 import android.app.AlertDialog
 import android.app.admin.DevicePolicyManager
+import android.content.Context
 import android.content.Intent
 import android.util.Log
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.dock.DockManager
@@ -75,6 +78,7 @@
     private val devicePolicyManager: DevicePolicyManager,
     private val dockManager: DockManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Application private val appContext: Context,
 ) {
     private val isUsingRepository: Boolean
         get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
@@ -408,7 +412,8 @@
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
                 value =
                     !isFeatureDisabledByDevicePolicy() &&
-                        featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
+                        featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) &&
+                        appContext.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled),
             ),
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index 6170180..d0bc25f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -36,8 +36,8 @@
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
-/** Hosts business and application state accessing logic for the lock screen scene. */
-class LockScreenSceneInteractor
+/** Hosts business and application state accessing logic for the lockscreen scene. */
+class LockscreenSceneInteractor
 @AssistedInject
 constructor(
     @Application applicationScope: CoroutineScope,
@@ -59,7 +59,7 @@
                 initialValue = !authenticationInteractor.isUnlocked.value,
             )
 
-    /** Whether it's currently possible to swipe up to dismiss the lock screen. */
+    /** Whether it's currently possible to swipe up to dismiss the lockscreen. */
     val isSwipeToDismissEnabled: StateFlow<Boolean> =
         combine(
                 authenticationInteractor.isUnlocked,
@@ -81,9 +81,9 @@
             )
 
     init {
-        // LOCKING SHOWS LOCK SCREEN.
+        // LOCKING SHOWS Lockscreen.
         //
-        // Move to the lock screen scene if the device becomes locked while in any scene.
+        // Move to the lockscreen scene if the device becomes locked while in any scene.
         applicationScope.launch {
             authenticationInteractor.isUnlocked
                 .map { !it }
@@ -92,7 +92,7 @@
                     if (isLocked) {
                         sceneInteractor.setCurrentScene(
                             containerName = containerName,
-                            scene = SceneModel(SceneKey.LockScreen),
+                            scene = SceneModel(SceneKey.Lockscreen),
                         )
                     }
                 }
@@ -101,7 +101,7 @@
         // BYPASS UNLOCK.
         //
         // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
-        // lock screen scene.
+        // lockscreen scene.
         applicationScope.launch {
             combine(
                     authenticationInteractor.isBypassEnabled,
@@ -110,7 +110,7 @@
                     ::Triple,
                 )
                 .collect { (isBypassEnabled, isUnlocked, currentScene) ->
-                    if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.LockScreen) {
+                    if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) {
                         sceneInteractor.setCurrentScene(
                             containerName = containerName,
                             scene = SceneModel(SceneKey.Gone),
@@ -119,9 +119,9 @@
                 }
         }
 
-        // SWIPE TO DISMISS LOCK SCREEN.
+        // SWIPE TO DISMISS Lockscreen.
         //
-        // If switched from the lock screen to the gone scene and the auth method was a swipe,
+        // If switched from the lockscreen to the gone scene and the auth method was a swipe,
         // unlocks the device.
         applicationScope.launch {
             combine(
@@ -133,7 +133,7 @@
                     val (previousScene, currentScene) = scenes
                     if (
                         authMethod is AuthenticationMethodModel.Swipe &&
-                            previousScene.key == SceneKey.LockScreen &&
+                            previousScene.key == SceneKey.Lockscreen &&
                             currentScene.key == SceneKey.Gone
                     ) {
                         authenticationInteractor.unlockDevice()
@@ -141,9 +141,9 @@
                 }
         }
 
-        // DISMISS LOCK SCREEN IF AUTH METHOD IS REMOVED.
+        // DISMISS Lockscreen IF AUTH METHOD IS REMOVED.
         //
-        // If the auth method becomes None while on the lock screen scene, dismisses the lock
+        // If the auth method becomes None while on the lockscreen scene, dismisses the lock
         // screen.
         applicationScope.launch {
             combine(
@@ -153,7 +153,7 @@
                 )
                 .collect { (authMethod, scene) ->
                     if (
-                        scene.key == SceneKey.LockScreen &&
+                        scene.key == SceneKey.Lockscreen &&
                             authMethod == AuthenticationMethodModel.None
                     ) {
                         sceneInteractor.setCurrentScene(
@@ -165,8 +165,8 @@
         }
     }
 
-    /** Attempts to dismiss the lock screen. This will cause the bouncer to show, if needed. */
-    fun dismissLockScreen() {
+    /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
+    fun dismissLockscreen() {
         bouncerInteractor.showOrUnlockDevice(containerName = containerName)
     }
 
@@ -181,6 +181,6 @@
     interface Factory {
         fun create(
             containerName: String,
-        ): LockScreenSceneInteractor
+        ): LockscreenSceneInteractor
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 233146a5..54bc349 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.shared.system.SysUiStatsLog
@@ -196,6 +197,7 @@
         cancelShowRunnable()
         repository.setPrimaryShowingSoon(false)
         repository.setPrimaryShow(false)
+        repository.setPanelExpansion(EXPANSION_HIDDEN)
         primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
         Trace.endSection()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 0db4ab1..555a09b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -124,7 +124,9 @@
 
             setUpUdfps(rootView)
 
-            setUpClock(rootView)
+            if (!shouldHideClock) {
+                setUpClock(rootView)
+            }
 
             rootView.measure(
                 View.MeasureSpec.makeMeasureSpec(
@@ -352,28 +354,23 @@
         clockController.clock = clock
 
         colorOverride?.let { clock.events.onSeedColorChanged(it) }
-        if (!shouldHideClock) {
-            clock.largeClock.events.onTargetRegionChanged(
-                KeyguardClockSwitch.getLargeClockRegion(parentView)
-            )
 
-            clockView?.let { parentView.removeView(it) }
-            clockView =
-                clock.largeClock.view.apply {
-                    if (shouldHighlightSelectedAffordance) {
-                        alpha = DIM_ALPHA
-                    }
-                    parentView.addView(this)
-                    visibility = View.VISIBLE
+        clock.largeClock.events.onTargetRegionChanged(
+            KeyguardClockSwitch.getLargeClockRegion(parentView)
+        )
+
+        clockView?.let { parentView.removeView(it) }
+        clockView =
+            clock.largeClock.view.apply {
+                if (shouldHighlightSelectedAffordance) {
+                    alpha = DIM_ALPHA
                 }
-        } else {
-            clockView?.visibility = View.GONE
-        }
+                parentView.addView(this)
+                visibility = View.VISIBLE
+            }
 
         // Hide smart space if the clock has weather display; otherwise show it
-        val hasCustomWeatherDataDisplay =
-            clock.largeClock.config.hasCustomWeatherDataDisplay == true
-        hideSmartspace(hasCustomWeatherDataDisplay)
+        hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 08b9613..f212a55 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -31,17 +31,17 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
-/** Models UI state and handles user input for the lock screen scene. */
-class LockScreenSceneViewModel
+/** Models UI state and handles user input for the lockscreen scene. */
+class LockscreenSceneViewModel
 @AssistedInject
 constructor(
     @Application applicationScope: CoroutineScope,
-    interactorFactory: LockScreenSceneInteractor.Factory,
+    interactorFactory: LockscreenSceneInteractor.Factory,
     @Assisted containerName: String,
 ) {
-    private val interactor: LockScreenSceneInteractor = interactorFactory.create(containerName)
+    private val interactor: LockscreenSceneInteractor = interactorFactory.create(containerName)
 
-    /** The icon for the "lock" button on the lock screen. */
+    /** The icon for the "lock" button on the lockscreen. */
     val lockButtonIcon: StateFlow<Icon> =
         interactor.isDeviceLocked
             .map { isLocked -> lockIcon(isLocked = isLocked) }
@@ -63,12 +63,12 @@
 
     /** Notifies that the lock button on the lock screen was clicked. */
     fun onLockButtonClicked() {
-        interactor.dismissLockScreen()
+        interactor.dismissLockscreen()
     }
 
     /** Notifies that some content on the lock screen was clicked. */
     fun onContentClicked() {
-        interactor.dismissLockScreen()
+        interactor.dismissLockscreen()
     }
 
     private fun upDestinationSceneKey(
@@ -103,6 +103,6 @@
     interface Factory {
         fun create(
             containerName: String,
-        ): LockScreenSceneViewModel
+        ): LockscreenSceneViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index edab56e..1fd11bd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -323,7 +323,6 @@
                         if (isFlungAwayFromEdge(endX = event.x) ||
                             previousXTranslation > params.staticTriggerThreshold
                         ) {
-                            updateArrowState(GestureState.ACTIVE)
                             updateArrowState(GestureState.FLUNG)
                         } else {
                             updateArrowState(GestureState.CANCELLED)
@@ -331,8 +330,11 @@
                     }
                     GestureState.INACTIVE -> {
                         if (isFlungAwayFromEdge(endX = event.x)) {
+                            // This is called outside of updateArrowState so that
+                            // BackAnimationController can immediately evaluate state
+                            // instead of after the flung delay
+                            backCallback.setTriggerBack(true)
                             mainHandler.postDelayed(MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION) {
-                                updateArrowState(GestureState.ACTIVE)
                                 updateArrowState(GestureState.FLUNG)
                             }
                         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 42de7f0..5818fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -63,6 +63,8 @@
 import android.view.WindowManager;
 import android.window.BackEvent;
 
+import androidx.annotation.DimenRes;
+
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.R;
@@ -225,7 +227,8 @@
     // The slop to distinguish between horizontal and vertical motion
     private float mTouchSlop;
     // The threshold for back swipe full progress.
-    private float mBackSwipeProgressThreshold;
+    private float mBackSwipeLinearThreshold;
+    private float mNonLinearFactor;
     // Duration after which we consider the event as longpress.
     private final int mLongPressTimeout;
     private int mStartingQuickstepRotation = -1;
@@ -471,11 +474,19 @@
         final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
                         SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f);
         mTouchSlop = mViewConfiguration.getScaledTouchSlop() * backGestureSlop;
-        mBackSwipeProgressThreshold = res.getDimension(
+        mBackSwipeLinearThreshold = res.getDimension(
                 R.dimen.navigation_edge_action_progress_threshold);
+        mNonLinearFactor = getDimenFloat(res,
+                R.dimen.back_progress_non_linear_factor);
         updateBackAnimationThresholds();
     }
 
+    private float getDimenFloat(Resources res, @DimenRes int resId) {
+        TypedValue typedValue = new TypedValue();
+        res.getValue(resId, typedValue, true);
+        return typedValue.getFloat();
+    }
+
     public void updateNavigationBarOverlayExcludeRegion(Rect exclude) {
         mNavBarOverlayExcludedBounds.set(exclude);
     }
@@ -1116,8 +1127,9 @@
         if (mBackAnimation == null) {
             return;
         }
-        mBackAnimation.setSwipeThresholds(
-                Math.min(mDisplaySize.x, mBackSwipeProgressThreshold));
+        int maxDistance = mDisplaySize.x;
+        float linearDistance = Math.min(maxDistance, mBackSwipeLinearThreshold);
+        mBackAnimation.setSwipeThresholds(linearDistance, maxDistance, mNonLinearFactor);
     }
 
     private boolean sendEvent(int action, int code) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 182ece7..6d881d5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -132,7 +132,7 @@
 
         entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
         entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
-        activeWidthInterpolator = PathInterpolator(.56f, -0.39f, .18f, 1.46f)
+        activeWidthInterpolator = PathInterpolator(.7f, -0.24f, .48f, 1.21f)
         arrowAngleInterpolator = entryWidthInterpolator
         horizontalTranslationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
         verticalTranslationInterpolator = PathInterpolator(.5f, 1.15f, .41f, .94f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 6525a98..36dec1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -25,15 +25,15 @@
 class QuickSettingsSceneViewModel
 @AssistedInject
 constructor(
-    lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
+    lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
     @Assisted containerName: String,
 ) {
-    private val lockScreenSceneInteractor: LockScreenSceneInteractor =
-        lockScreenSceneInteractorFactory.create(containerName)
+    private val lockscreenSceneInteractor: LockscreenSceneInteractor =
+        lockscreenSceneInteractorFactory.create(containerName)
 
     /** Notifies that some content in quick settings was clicked. */
     fun onContentClicked() {
-        lockScreenSceneInteractor.dismissLockScreen()
+        lockscreenSceneInteractor.dismissLockscreen()
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
index 9ef439d..e7811e3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
@@ -32,8 +32,8 @@
      */
     object Gone : SceneKey("gone")
 
-    /** The lock screen is the scene that shows when the device is locked. */
-    object LockScreen : SceneKey("lockscreen")
+    /** The lockscreen is the scene that shows when the device is locked. */
+    object Lockscreen : SceneKey("lockscreen")
 
     /**
      * The shade is the scene whose primary purpose is to show a scrollable list of notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
index 2336673..9235fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -84,7 +84,7 @@
                 Color.YELLOW, "calculatePanelHeightShade()");
         drawDebugInfo(canvas,
                 (int) mQsController.calculateNotificationsTopPadding(
-                        mNotificationPanelViewController.isExpanding(),
+                        mNotificationPanelViewController.isExpandingOrCollapsing(),
                         mNotificationPanelViewController.getKeyguardNotificationStaticPadding(),
                         mNotificationPanelViewController.getExpandedFraction()),
                 Color.MAGENTA, "calculateNotificationsTopPadding()");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index af12bc2..0fdd7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -233,7 +233,6 @@
 import javax.inject.Provider;
 
 import kotlin.Unit;
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 @CentralSurfacesComponent.CentralSurfacesScope
@@ -415,7 +414,11 @@
     private final KeyguardClockPositionAlgorithm.Result
             mClockPositionResult =
             new KeyguardClockPositionAlgorithm.Result();
-    private boolean mIsExpanding;
+    /**
+     * Indicates shade (or just QS) is expanding or collapsing but doesn't fully cover KEYGUARD
+     * state when shade can be expanded with swipe down or swipe down from the top to full QS.
+     */
+    private boolean mIsExpandingOrCollapsing;
 
     /**
      * Indicates drag starting height when swiping down or up on heads-up notifications.
@@ -1862,7 +1865,7 @@
 
     @Override
     public void expandToNotifications() {
-        if (mSplitShadeEnabled && (isShadeFullyExpanded() || isExpanding())) {
+        if (mSplitShadeEnabled && (isShadeFullyExpanded() || isExpandingOrCollapsing())) {
             return;
         }
         if (mQsController.getExpanded()) {
@@ -2109,6 +2112,9 @@
                     ? QUICK_SETTINGS : (
                     mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
             if (!isFalseTouch(x, y, interactionType)) {
+                mShadeLog.logFlingExpands(vel, vectorVel, interactionType,
+                        this.mFlingAnimationUtils.getMinVelocityPxPerSecond(),
+                        mExpandedFraction > 0.5f, mAllowExpandForSmallExpansion);
                 if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
                     expands = shouldExpandWhenNotFlinging();
                 } else {
@@ -2269,7 +2275,7 @@
 
     void requestScrollerTopPaddingUpdate(boolean animate) {
         mNotificationStackScrollLayoutController.updateTopPadding(
-                mQsController.calculateNotificationsTopPadding(mIsExpanding,
+                mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
                         getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
         if (isKeyguardShowing()
                 && mKeyguardBypassController.getBypassEnabled()) {
@@ -2322,7 +2328,7 @@
         }
         int maxHeight;
         if (mQsController.isExpandImmediate() || mQsController.getExpanded()
-                || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()
+                || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()
                 || mPulsing || mSplitShadeEnabled) {
             maxHeight = mQsController.calculatePanelHeightExpanded(
                     mClockPositionResult.stackScrollerPadding);
@@ -2342,8 +2348,11 @@
         return maxHeight;
     }
 
-    public boolean isExpanding() {
-        return mIsExpanding;
+    @Override
+    public boolean isExpandingOrCollapsing() {
+        float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress();
+        return mIsExpandingOrCollapsing
+                || (0 < lockscreenExpansionProgress && lockscreenExpansionProgress < 1);
     }
 
     private void onHeightUpdated(float expandedHeight) {
@@ -2355,7 +2364,7 @@
                     mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
         }
         if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
-                || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()) {
+                || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) {
             // Updating the clock position will set the top padding which might
             // trigger a new panel height and re-position the clock.
             // This is a circular dependency and should be avoided, otherwise we'll have
@@ -2493,7 +2502,7 @@
         mNotificationStackScrollLayoutController.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
-        mIsExpanding = false;
+        mIsExpandingOrCollapsing = false;
         mMediaHierarchyManager.setCollapsingShadeFromQS(false);
         mMediaHierarchyManager.setQsExpanded(mQsController.getExpanded());
         if (isFullyCollapsed()) {
@@ -2908,6 +2917,10 @@
                 && mBarState == StatusBarState.SHADE;
     }
 
+    private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
+        return mUnlockedScreenOffAnimationController.isAnimationPlaying();
+    }
+
     @Override
     public boolean shouldHideStatusBarIconsWhenExpanded() {
         if (mIsLaunchAnimationRunning) {
@@ -3199,7 +3212,7 @@
         ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
         ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
         ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
-        ipw.print("mIsExpanding="); ipw.println(mIsExpanding);
+        ipw.print("mIsExpandingOrCollapsing="); ipw.println(mIsExpandingOrCollapsing);
         ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight);
         ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp);
         ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight);
@@ -3431,7 +3444,7 @@
     void notifyExpandingStarted() {
         if (!mExpanding) {
             mExpanding = true;
-            mIsExpanding = true;
+            mIsExpandingOrCollapsing = true;
             mQsController.onExpandingStarted(mQsController.getFullyExpanded());
         }
     }
@@ -3492,7 +3505,7 @@
      *                         gesture), we always play haptic.
      */
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
-        if (mVibrateOnOpening) {
+        if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) {
             if (!openingWithTouch || !mHasVibratedOnOpen) {
                 mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 mHasVibratedOnOpen = true;
@@ -3792,7 +3805,7 @@
         } else if (mBarState == SHADE_LOCKED) {
             return true;
         } else {
-            // case of two finger swipe from the top of keyguard
+            // case of swipe from the top of keyguard to expanded QS
             return mQsController.computeExpansionFraction() == 1;
         }
     }
@@ -3967,6 +3980,7 @@
                 || isPanelVisibleBecauseOfHeadsUp()
                 || mTracking
                 || mHeightAnimator != null
+                || isPanelVisibleBecauseScrimIsAnimatingOff()
                 && !mIsSpringBackAnimation;
     }
 
@@ -4044,7 +4058,7 @@
      * shade QS are always expanded
      */
     private void closeQsIfPossible() {
-        boolean openOrOpening = isShadeFullyExpanded() || isExpanding();
+        boolean openOrOpening = isShadeFullyExpanded() || isExpandingOrCollapsing();
         if (!(mSplitShadeEnabled && openOrOpening)) {
             mQsController.closeQs();
         }
@@ -4767,7 +4781,7 @@
 
             // If pulse is expanding already, let's give it the touch. There are situations
             // where the panel starts expanding even though we're also pulsing
-            boolean pulseShouldGetTouch = (!mIsExpanding
+            boolean pulseShouldGetTouch = (!mIsExpandingOrCollapsing
                     && !mQsController.shouldQuickSettingsIntercept(mDownX, mDownY, 0))
                     || mPulseExpansionHandler.isExpanding();
             if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
@@ -4933,7 +4947,7 @@
                         mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
                     }
                     addMovement(event);
-                    if (!isFullyCollapsed() && !isOnKeyguard()) {
+                    if (!isFullyCollapsed()) {
                         maybeVibrateOnOpening(true /* openingWithTouch */);
                     }
                     float h = y - mInitialExpandY;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index e08bc33..d0a3cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -78,6 +78,11 @@
     boolean isShadeFullyOpen();
 
     /**
+     * Returns whether shade or QS are currently opening or collapsing.
+     */
+    boolean isExpandingOrCollapsing();
+
+    /**
      * Add a runnable for NotificationPanelView to post when the panel is expanded.
      *
      * @param action the action to post
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index c71467b..d00dab6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -164,6 +164,11 @@
     }
 
     @Override
+    public boolean isExpandingOrCollapsing() {
+        return mNotificationPanelViewController.isExpandingOrCollapsing();
+    }
+
+    @Override
     public void postOnShadeExpanded(Runnable executable) {
         mNotificationPanelViewController.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 25073c1b..2b772e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -253,6 +253,31 @@
         )
     }
 
+    fun logFlingExpands(
+            vel: Float,
+            vectorVel: Float,
+            interactionType: Int,
+            minVelocityPxPerSecond: Float,
+            expansionOverHalf: Boolean,
+            allowExpandForSmallExpansion: Boolean
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                int1 = interactionType
+                long1 = vel.toLong()
+                long2 = vectorVel.toLong()
+                double1 = minVelocityPxPerSecond.toDouble()
+                bool1 = expansionOverHalf
+                bool2 = allowExpandForSmallExpansion
+            },
+            { "NPVC flingExpands called with vel: $long1, vectorVel: $long2, " +
+                    "interactionType: $int1, minVelocityPxPerSecond: $double1 " +
+                    "expansionOverHalf: $bool1, allowExpandForSmallExpansion: $bool2" }
+        )
+    }
+
     fun flingQs(flingType: Int, isClick: Boolean) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index d5a9e95..f75047c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -48,7 +48,7 @@
     fun expandToNotifications()
 
     /** Returns whether the shade is expanding or collapsing itself or quick settings. */
-    val isExpanding: Boolean
+    val isExpandingOrCollapsing: Boolean
 
     /**
      * Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 0ebcfa2..fc1e87a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -141,6 +141,7 @@
         mCarrierTextManager = carrierTextManagerBuilder
                 .setShowAirplaneMode(false)
                 .setShowMissingSim(false)
+                .setDebugLocationString("Shade")
                 .build();
         mCarrierConfigTracker = carrierConfigTracker;
         mSlotIndexResolver = slotIndexResolver;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index dcae258..8a96a47 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -33,11 +33,11 @@
 @AssistedInject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
+    lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
     @Assisted private val containerName: String,
 ) {
-    private val lockScreenInteractor: LockScreenSceneInteractor =
-        lockScreenSceneInteractorFactory.create(containerName)
+    private val lockScreenInteractor: LockscreenSceneInteractor =
+        lockscreenSceneInteractorFactory.create(containerName)
 
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -54,13 +54,13 @@
 
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() {
-        lockScreenInteractor.dismissLockScreen()
+        lockScreenInteractor.dismissLockscreen()
     }
 
     private fun upDestinationSceneKey(
         isLocked: Boolean,
     ): SceneKey {
-        return if (isLocked) SceneKey.LockScreen else SceneKey.Gone
+        return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ae7c216..b0f3f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -93,6 +93,12 @@
     private boolean mAppearing;
     private float mPulseHeight = MAX_PULSE_HEIGHT;
 
+    /**
+     * The ExpandableNotificationRow that is pulsing, or the one that was pulsing
+     * when the device started to transition from AOD to LockScreen.
+     */
+    private ExpandableNotificationRow mPulsingRow;
+
     /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
     private float mFractionToShade;
 
@@ -564,6 +570,19 @@
         return mPulsing && entry.isAlerting();
     }
 
+    public void setPulsingRow(ExpandableNotificationRow row) {
+        mPulsingRow = row;
+    }
+
+    /**
+     * @param row The row to check
+     * @return true if row is the pulsing row when the device started to transition from AOD to lock
+     * screen
+     */
+    public boolean isPulsingRow(ExpandableView row) {
+        return mPulsingRow == row;
+    }
+
     public boolean isPanelTracking() {
         return mPanelTracking;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 92d767a..6f1c378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -548,7 +548,7 @@
         ExpandableViewState viewState = view.getViewState();
         viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
 
-        final float expansionFraction = getExpansionFractionWithoutShelf(
+        float expansionFraction = getExpansionFractionWithoutShelf(
                 algorithmState, ambientState);
 
         // Add gap between sections.
@@ -619,6 +619,11 @@
                     updateViewWithShelf(view, viewState, shelfStart);
                 }
             }
+            // Avoid pulsing notification flicker during AOD to LS
+            // A pulsing notification is already expanded, no need to expand it again with animation
+            if (ambientState.isPulsingRow(view)) {
+                expansionFraction = 1.0f;
+            }
             // Clip height of view right before shelf.
             viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
         }
@@ -700,9 +705,11 @@
                 && !(child instanceof FooterView);
     }
 
-    private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
+    @VisibleForTesting
+    void updatePulsingStates(StackScrollAlgorithmState algorithmState,
                                      AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
+        ExpandableNotificationRow pulsingRow = null;
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
             if (!(child instanceof ExpandableNotificationRow)) {
@@ -714,6 +721,19 @@
             }
             ExpandableViewState viewState = row.getViewState();
             viewState.hidden = false;
+            pulsingRow = row;
+        }
+
+        // Set AmbientState#pulsingRow to the current pulsing row when on AOD.
+        // Set AmbientState#pulsingRow=null when on lockscreen, since AmbientState#pulsingRow
+        // is only used for skipping the unfurl animation for (the notification that was already
+        // showing at full height on AOD) during the AOD=>lockscreen transition, where
+        // dozeAmount=[1f, 0f). We also need to reset the pulsingRow once it is no longer used
+        // because it will interfere with future unfurling animations - for example, during the
+        // LS=>AOD animation, the pulsingRow may stay at full height when it should squish with the
+        // rest of the stack.
+        if (ambientState.getDozeAmount() == 0.0f || ambientState.getDozeAmount() == 1.0f) {
+            ambientState.setPulsingRow(pulsingRow);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 37e77766..0ccc819 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -339,7 +339,7 @@
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count("panel_open", 1);
             } else if (!mQsController.getExpanded()
-                    && !mShadeViewController.isExpanding()) {
+                    && !mShadeViewController.isExpandingOrCollapsing()) {
                 mQsController.flingQs(0 /* velocity */,
                         ShadeViewController.FLING_EXPAND);
                 mMetricsLogger.count("panel_open_qs", 1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 263566e..5c99f34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1224,6 +1224,7 @@
         // By default turning off the screen also closes the shade.
         // We want to make sure that the shade status is kept after folding/unfolding.
         boolean isShadeOpen = mShadeController.isShadeFullyOpen();
+        boolean isShadeExpandingOrCollapsing = mShadeController.isExpandingOrCollapsing();
         boolean leaveOpen = isShadeOpen && !willGoToSleep && mState == SHADE;
         if (DEBUG) {
             Log.d(TAG, String.format(
@@ -1231,14 +1232,15 @@
                             + "isFolded=%s, "
                             + "willGoToSleep=%s, "
                             + "isShadeOpen=%s, "
+                            + "isShadeExpandingOrCollapsing=%s, "
                             + "leaveOpen=%s",
-                    isFolded, willGoToSleep, isShadeOpen, leaveOpen));
+                    isFolded, willGoToSleep, isShadeOpen, isShadeExpandingOrCollapsing, leaveOpen));
         }
         if (leaveOpen) {
             // below makes shade stay open when going from folded to unfolded
             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        if (mState != SHADE && isShadeOpen) {
+        if (mState != SHADE && (isShadeOpen || isShadeExpandingOrCollapsing)) {
             // When device state changes on KEYGUARD/SHADE_LOCKED we don't want to keep the state of
             // the shade and instead we open clean state of keyguard with shade closed.
             // Normally some parts of QS state (like expanded/collapsed) are persisted and
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 f2fbd7d..414a2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -488,7 +488,7 @@
         final boolean hideBouncerOverDream =
                 mDreamOverlayStateController.isOverlayActive()
                         && (mShadeViewController.isExpanded()
-                        || mShadeViewController.isExpanding());
+                        || mShadeViewController.isExpandingOrCollapsing());
 
         final boolean isUserTrackingStarted =
                 event.getFraction() != EXPANSION_HIDDEN && event.getTracking();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 1559c64..d2e5a45 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
+import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -691,6 +692,14 @@
         verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
     }
 
+    @Test
+    public void setExpansion_setsAlpha() {
+        mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
+
+        verify(mView).setAlpha(1f);
+        verify(mView).setTranslationY(0f);
+    }
+
     private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
         mKeyguardSecurityContainerController.onViewAttached();
         verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
index 3cbb249..63b0b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
@@ -29,6 +29,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
 
@@ -73,6 +74,9 @@
     private Context mContext;
 
     @Mock
+    private Resources mResources;
+
+    @Mock
     private ControlsComponent mControlsComponent;
 
     @Mock
@@ -118,7 +122,7 @@
     @Test
     public void complicationType() {
         final DreamHomeControlsComplication complication =
-                new DreamHomeControlsComplication(mComponentFactory);
+                new DreamHomeControlsComplication(mResources, mComponentFactory);
         assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
                 COMPLICATION_TYPE_HOME_CONTROLS);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 9cf988e..8ee7d3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -30,6 +30,7 @@
 import android.view.SurfaceControlViewHost
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -67,6 +68,7 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -103,6 +105,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
         whenever(previewRenderer.surfacePackage).thenReturn(previewSurfacePackage)
         whenever(previewRendererFactory.create(any())).thenReturn(previewRenderer)
         whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper)
@@ -195,6 +198,7 @@
                 devicePolicyManager = devicePolicyManager,
                 dockManager = dockManager,
                 backgroundDispatcher = testDispatcher,
+                appContext = mContext,
             )
         underTest.previewManager =
             KeyguardRemotePreviewManager(
@@ -216,6 +220,13 @@
         )
     }
 
+    @After
+    fun tearDown() {
+        mContext
+            .getOrCreateTestableResources()
+            .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled)
+    }
+
     @Test
     fun onAttachInfo_reportsContext() {
         val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 0a9618c..688c2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -114,7 +114,6 @@
     @After
     fun tearDown() {
         keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true)
-        keyguardUnlockAnimationController.wallpaperAlphaAnimator.cancel()
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index dfef947..5de24e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -39,6 +40,7 @@
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,6 +67,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true)
         whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
             it.arguments[0]
         }
@@ -76,6 +79,13 @@
         runBlocking { createUnderTest() }
     }
 
+    @After
+    fun tearDown() {
+        mContext
+            .getOrCreateTestableResources()
+            .removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled)
+    }
+
     @Test
     fun isEnabled() =
         testScope.runTest {
@@ -108,6 +118,17 @@
         }
 
     @Test
+    fun isEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() =
+        testScope.runTest {
+            overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, false)
+            createUnderTest()
+            val isEnabled by collectLastValue(underTest.isLongPressHandlingEnabled)
+            runCurrent()
+
+            assertThat(isEnabled).isFalse()
+        }
+
+    @Test
     fun longPressed_menuClicked_showsSettings() =
         testScope.runTest {
             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
@@ -267,6 +288,7 @@
     ) {
         underTest =
             KeyguardLongPressInteractor(
+                appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
                     KeyguardTransitionInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index a75e11a..fb21847 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -340,6 +340,7 @@
                 devicePolicyManager = devicePolicyManager,
                 dockManager = dockManager,
                 backgroundDispatcher = testDispatcher,
+                appContext = mContext,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 3336e3b..5d2c3ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -200,6 +200,7 @@
                 devicePolicyManager = devicePolicyManager,
                 dockManager = dockManager,
                 backgroundDispatcher = testDispatcher,
+                appContext = mContext,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index c2c528a..d622f1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -36,7 +36,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
-class LockScreenSceneInteractorTest : SysuiTestCase() {
+class LockscreenSceneInteractorTest : SysuiTestCase() {
 
     private val testScope = TestScope()
     private val utils = SceneTestUtils(this, testScope)
@@ -96,9 +96,9 @@
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
             authenticationInteractor.lockDevice()
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            underTest.dismissLockScreen()
+            underTest.dismissLockscreen()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -109,9 +109,9 @@
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
             authenticationInteractor.unlockDevice()
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            underTest.dismissLockScreen()
+            underTest.dismissLockscreen()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -122,9 +122,9 @@
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
             authenticationInteractor.lockDevice()
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            underTest.dismissLockScreen()
+            underTest.dismissLockscreen()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -142,7 +142,7 @@
 
             authenticationInteractor.lockDevice()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
         }
 
     @Test
@@ -150,11 +150,11 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
             authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
             if (!authenticationInteractor.isBypassEnabled.value) {
                 authenticationInteractor.toggleBypassEnabled()
             }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             authenticationInteractor.biometricUnlock()
 
@@ -166,22 +166,22 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
             authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
             if (authenticationInteractor.isBypassEnabled.value) {
                 authenticationInteractor.toggleBypassEnabled()
             }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             authenticationInteractor.biometricUnlock()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
         }
 
     @Test
     fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             assertThat(isUnlocked).isFalse()
 
@@ -194,7 +194,7 @@
     fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
             assertThat(isUnlocked).isFalse()
 
@@ -223,9 +223,9 @@
     fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 5d39794..b5cb44a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -159,6 +159,7 @@
         verify(repository).setPrimaryShow(false)
         verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
         verify(repository).setPrimaryStartDisappearAnimation(null)
+        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 69d43af..8a36dbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -211,6 +211,7 @@
             )
         val keyguardLongPressInteractor =
             KeyguardLongPressInteractor(
+                appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
                     KeyguardTransitionInteractor(
@@ -240,6 +241,7 @@
                         devicePolicyManager = devicePolicyManager,
                         dockManager = dockManager,
                         backgroundDispatcher = testDispatcher,
+                        appContext = mContext,
                     ),
                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 burnInHelperWrapper = burnInHelperWrapper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 9e8be3e..8ba3f0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
@@ -39,7 +39,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
-class LockScreenSceneViewModelTest : SysuiTestCase() {
+class LockscreenSceneViewModelTest : SysuiTestCase() {
 
     private val testScope = TestScope()
     private val utils = SceneTestUtils(this, testScope)
@@ -50,11 +50,11 @@
         )
 
     private val underTest =
-        LockScreenSceneViewModel(
+        LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
             interactorFactory =
-                object : LockScreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockScreenSceneInteractor {
+                object : LockscreenSceneInteractor.Factory {
+                    override fun create(containerName: String): LockscreenSceneInteractor {
                         return utils.lockScreenSceneInteractor(
                             authenticationInteractor = authenticationInteractor,
                             sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 3f838e6..105387d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
@@ -49,9 +49,9 @@
 
     private val underTest =
         QuickSettingsSceneViewModel(
-            lockScreenSceneInteractorFactory =
-                object : LockScreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockScreenSceneInteractor {
+            lockscreenSceneInteractorFactory =
+                object : LockscreenSceneInteractor.Factory {
+                    override fun create(containerName: String): LockscreenSceneInteractor {
                         return utils.lockScreenSceneInteractor(
                             authenticationInteractor = authenticationInteractor,
                             sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 6c7017bac..de15c77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -45,7 +45,7 @@
                 listOf(
                     SceneKey.QuickSettings,
                     SceneKey.Shade,
-                    SceneKey.LockScreen,
+                    SceneKey.Lockscreen,
                     SceneKey.Bouncer,
                     SceneKey.Gone,
                 )
@@ -62,7 +62,7 @@
     fun currentScene() = runTest {
         val underTest = utils.fakeSceneContainerRepository()
         val currentScene by collectLastValue(underTest.currentScene("container1"))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
         underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
@@ -88,7 +88,7 @@
                     utils.fakeSceneContainerConfig("container1"),
                     utils.fakeSceneContainerConfig(
                         "container2",
-                        listOf(SceneKey.QuickSettings, SceneKey.LockScreen)
+                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
                     ),
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index cf99e3b..ee4f6c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -46,7 +46,7 @@
     @Test
     fun sceneTransitions() = runTest {
         val currentScene by collectLastValue(underTest.currentScene("container1"))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
         underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 6105c87..cd2f5af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -63,7 +63,7 @@
     @Test
     fun sceneTransition() = runTest {
         val currentScene by collectLastValue(underTest.currentScene)
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
         underTest.setCurrentScene(SceneModel(SceneKey.Shade))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 48e0b53..a5a9de5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -902,6 +902,13 @@
     }
 
     @Test
+    public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() {
+        when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f);
+        assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue();
+    }
+
+
+    @Test
     public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
         enableSplitShade(true);
         mNotificationPanelViewController.expandToQs();
@@ -1099,7 +1106,7 @@
     }
 
     @Test
-    public void shadeExpanded_inShadeState() {
+    public void shadeFullyExpanded_inShadeState() {
         mStatusBarStateController.setState(SHADE);
 
         mNotificationPanelViewController.setExpandedHeight(0);
@@ -1111,7 +1118,7 @@
     }
 
     @Test
-    public void shadeExpanded_onKeyguard() {
+    public void shadeFullyExpanded_onKeyguard() {
         mStatusBarStateController.setState(KEYGUARD);
 
         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
@@ -1120,8 +1127,39 @@
     }
 
     @Test
-    public void shadeExpanded_onShadeLocked() {
+    public void shadeFullyExpanded_onShadeLocked() {
         mStatusBarStateController.setState(SHADE_LOCKED);
         assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
     }
+
+    @Test
+    public void shadeExpanded_whenHasHeight() {
+        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+    }
+
+    @Test
+    public void shadeExpanded_whenInstantExpanding() {
+        mNotificationPanelViewController.expand(true);
+        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+    }
+
+    @Test
+    public void shadeExpanded_whenHunIsPresent() {
+        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
+        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+    }
+
+    @Test
+    public void shadeExpanded_whenWaitingForExpandGesture() {
+        mNotificationPanelViewController.startWaitingForExpandGesture();
+        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+    }
+
+    @Test
+    public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() {
+        when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
+        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 2ef3d60..57ae621 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -109,6 +110,8 @@
                 .thenReturn(mCarrierTextControllerBuilder);
         when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
                 .thenReturn(mCarrierTextControllerBuilder);
+        when(mCarrierTextControllerBuilder.setDebugLocationString(anyString()))
+                .thenReturn(mCarrierTextControllerBuilder);
         when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager);
 
         doAnswer(invocation -> mCallback = invocation.getArgument(0))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 2e7f83d..69d03d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
@@ -50,9 +50,9 @@
     private val underTest =
         ShadeSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            lockScreenSceneInteractorFactory =
-                object : LockScreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockScreenSceneInteractor {
+            lockscreenSceneInteractorFactory =
+                object : LockscreenSceneInteractor.Factory {
+                    override fun create(containerName: String): LockscreenSceneInteractor {
                         return utils.lockScreenSceneInteractor(
                             authenticationInteractor = authenticationInteractor,
                             sceneInteractor = sceneInteractor,
@@ -74,7 +74,7 @@
             authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
             authenticationInteractor.lockDevice()
 
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.LockScreen)
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 7f20f1e..e12d179 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -716,6 +716,94 @@
                 .isLessThan(px(R.dimen.heads_up_pinned_elevation))
     }
 
+    @Test
+    fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowDoesNotChange() {
+        // Given: Before AOD to LockScreen, there was a pulsing notification
+        val pulsingNotificationView = createPulsingViewMock()
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(pulsingNotificationView)
+        ambientState.setPulsingRow(pulsingNotificationView)
+
+        // When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
+        // stage; here we use 0.5 for testing.
+        // stackScrollAlgorithm.updatePulsingStates is called
+        ambientState.dozeAmount = 0.5f
+        stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
+
+        // Then: ambientState.pulsingRow should still be pulsingNotificationView
+        assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
+    }
+
+    @Test
+    fun deviceOnAod_hasPulsingNotification_recordPulsingNotificationRow() {
+        // Given: Device is on AOD, there is a pulsing notification
+        // ambientState.pulsingRow is null before stackScrollAlgorithm.updatePulsingStates
+        ambientState.dozeAmount = 1.0f
+        val pulsingNotificationView = createPulsingViewMock()
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(pulsingNotificationView)
+        ambientState.setPulsingRow(null)
+
+        // When: stackScrollAlgorithm.updatePulsingStates is called
+        stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
+
+        // Then: ambientState.pulsingRow should record the pulsingNotificationView
+        assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
+    }
+
+    @Test
+    fun deviceOnLockScreen_hasPulsingNotificationBefore_clearPulsingNotificationRowRecord() {
+        // Given: Device finished AOD to LockScreen, there was a pulsing notification, and
+        // ambientState.pulsingRow was not null before AOD to LockScreen
+        // pulsingNotificationView.showingPulsing() returns false since the device is on LockScreen
+        ambientState.dozeAmount = 0.0f
+        val pulsingNotificationView = createPulsingViewMock()
+        whenever(pulsingNotificationView.showingPulsing()).thenReturn(false)
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(pulsingNotificationView)
+        ambientState.setPulsingRow(pulsingNotificationView)
+
+        // When: stackScrollAlgorithm.updatePulsingStates is called
+        stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
+
+        // Then: ambientState.pulsingRow should be null
+        assertTrue(ambientState.isPulsingRow(null))
+    }
+
+    @Test
+    fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowShowAtFullHeight() {
+        // Given: Before AOD to LockScreen, there was a pulsing notification
+        val pulsingNotificationView = createPulsingViewMock()
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(pulsingNotificationView)
+        ambientState.setPulsingRow(pulsingNotificationView)
+
+        // When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
+        // stage; here we use 0.5 for testing. The expansionFraction is also 0.5.
+        // stackScrollAlgorithm.resetViewStates is called.
+        ambientState.dozeAmount = 0.5f
+        setExpansionFractionWithoutShelfDuringAodToLockScreen(
+                ambientState,
+                algorithmState,
+                fraction = 0.5f
+        )
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        // Then: pulsingNotificationView should show at full height
+        assertEquals(
+                stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
+                pulsingNotificationView.viewState.height
+        )
+
+        // After: reset dozeAmount and expansionFraction
+        ambientState.dozeAmount = 0f
+        setExpansionFractionWithoutShelfDuringAodToLockScreen(
+                ambientState,
+                algorithmState,
+                fraction = 1f
+        )
+    }
+
     private fun createHunViewMock(
             isShadeOpen: Boolean,
             fullyVisible: Boolean,
@@ -744,6 +832,29 @@
                 headsUpIsVisible = fullyVisible
             }
 
+    private fun createPulsingViewMock(
+    ) =
+            mock<ExpandableNotificationRow>().apply {
+                whenever(this.viewState).thenReturn(ExpandableViewState())
+                whenever(this.showingPulsing()).thenReturn(true)
+            }
+
+    private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
+            ambientState: AmbientState,
+            algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
+            fraction: Float
+    ) {
+        // showingShelf: false
+        algorithmState.firstViewInShelf = null
+        // scrimPadding: 0, because device is on lock screen
+        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.dozeAmount = 0.0f
+        // set stackEndHeight and stackHeight
+        // ExpansionFractionWithoutShelf == stackHeight / stackEndHeight
+        ambientState.stackEndHeight = 100f
+        ambientState.stackHeight = ambientState.stackEndHeight * fraction
+    }
+
     private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
             expansionFraction: Float,
             expectedAlpha: Float,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c83769d..cf6d5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -335,6 +335,7 @@
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private final InitController mInitController = new InitController();
     private final DumpManager mDumpManager = new DumpManager();
+    private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
 
     @Before
     public void setup() throws Exception {
@@ -487,7 +488,7 @@
                 mUserSwitcherController,
                 mBatteryController,
                 mColorExtractor,
-                new ScreenLifecycle(mDumpManager),
+                mScreenLifecycle,
                 mWakefulnessLifecycle,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
@@ -554,6 +555,7 @@
                 return mViewRootImpl;
             }
         };
+        mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
         mCentralSurfaces.initShadeVisibilityListener();
         when(mViewRootImpl.getOnBackInvokedDispatcher())
                 .thenReturn(mOnBackInvokedDispatcher);
@@ -1253,6 +1255,32 @@
     }
 
     @Test
+    public void deviceStateChange_unfolded_shadeExpanding_onKeyguard_closesQS() {
+        setFoldedStates(FOLD_STATE_FOLDED);
+        setGoToSleepStates(FOLD_STATE_FOLDED);
+        mCentralSurfaces.setBarStateForTest(KEYGUARD);
+        when(mNotificationPanelViewController.isExpandingOrCollapsing()).thenReturn(true);
+
+        setDeviceState(FOLD_STATE_UNFOLDED);
+        mScreenLifecycle.dispatchScreenTurnedOff();
+
+        verify(mQuickSettingsController).closeQs();
+    }
+
+    @Test
+    public void deviceStateChange_unfolded_shadeExpanded_onKeyguard_closesQS() {
+        setFoldedStates(FOLD_STATE_FOLDED);
+        setGoToSleepStates(FOLD_STATE_FOLDED);
+        mCentralSurfaces.setBarStateForTest(KEYGUARD);
+        when(mNotificationPanelViewController.isShadeFullyExpanded()).thenReturn(true);
+
+        setDeviceState(FOLD_STATE_UNFOLDED);
+        mScreenLifecycle.dispatchScreenTurnedOff();
+
+        verify(mQuickSettingsController).closeQs();
+    }
+
+    @Test
     public void startActivityDismissingKeyguard_isShowingAndIsOccluded() {
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 5a350bb..be3d54a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.bouncer.data.repo.BouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.data.model.SceneContainerConfig
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -58,7 +58,7 @@
         return listOf(
             SceneKey.QuickSettings,
             SceneKey.Shade,
-            SceneKey.LockScreen,
+            SceneKey.Lockscreen,
             SceneKey.Bouncer,
             SceneKey.Gone,
         )
@@ -71,7 +71,7 @@
         return SceneContainerConfig(
             name = name,
             sceneKeys = sceneKeys,
-            initialSceneKey = SceneKey.LockScreen,
+            initialSceneKey = SceneKey.Lockscreen,
         )
     }
 
@@ -139,8 +139,8 @@
         authenticationInteractor: AuthenticationInteractor,
         sceneInteractor: SceneInteractor,
         bouncerInteractor: BouncerInteractor,
-    ): LockScreenSceneInteractor {
-        return LockScreenSceneInteractor(
+    ): LockscreenSceneInteractor {
+        return LockscreenSceneInteractor(
             applicationScope = applicationScope(),
             authenticationInteractor = authenticationInteractor,
             bouncerInteractorFactory =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 544828a..bfa397f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18894,12 +18894,14 @@
 
     @Override
     public void waitForBroadcastIdle() {
-        waitForBroadcastIdle(LOG_WRITER_INFO);
+        waitForBroadcastIdle(LOG_WRITER_INFO, false);
     }
 
-    public void waitForBroadcastIdle(@NonNull PrintWriter pw) {
+    void waitForBroadcastIdle(@NonNull PrintWriter pw, boolean flushBroadcastLoopers) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
-        BroadcastLoopers.waitForIdle(pw);
+        if (flushBroadcastLoopers) {
+            BroadcastLoopers.waitForIdle(pw);
+        }
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForIdle(pw);
         }
@@ -18912,7 +18914,7 @@
         waitForBroadcastBarrier(LOG_WRITER_INFO, false, false);
     }
 
-    public void waitForBroadcastBarrier(@NonNull PrintWriter pw,
+    void waitForBroadcastBarrier(@NonNull PrintWriter pw,
             boolean flushBroadcastLoopers, boolean flushApplicationThreads) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
         if (flushBroadcastLoopers) {
@@ -18930,7 +18932,7 @@
      * Wait for all pending {@link IApplicationThread} events to be processed in
      * all currently running apps.
      */
-    public void waitForApplicationBarrier(@NonNull PrintWriter pw) {
+    void waitForApplicationBarrier(@NonNull PrintWriter pw) {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
         final AtomicInteger pingCount = new AtomicInteger(0);
         final AtomicInteger pongCount = new AtomicInteger(0);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 979874e..add22bd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3447,7 +3447,17 @@
 
     int runWaitForBroadcastIdle(PrintWriter pw) throws RemoteException {
         pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
-        mInternal.waitForBroadcastIdle(pw);
+        boolean flushBroadcastLoopers = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--flush-broadcast-loopers")) {
+                flushBroadcastLoopers = true;
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInternal.waitForBroadcastIdle(pw, flushBroadcastLoopers);
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 87214de..030d596 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -247,6 +247,26 @@
     private static final long DEFAULT_DELAY_URGENT_MILLIS = -120_000;
 
     /**
+     * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts to
+     * foreground processes, typically a negative value to indicate they should be
+     * executed before most other pending broadcasts.
+     */
+    public long DELAY_FOREGROUND_PROC_MILLIS = DEFAULT_DELAY_FOREGROUND_PROC_MILLIS;
+    private static final String KEY_DELAY_FOREGROUND_PROC_MILLIS =
+            "bcast_delay_foreground_proc_millis";
+    private static final long DEFAULT_DELAY_FOREGROUND_PROC_MILLIS = -120_000;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts to
+     * persistent processes, typically a negative value to indicate they should be
+     * executed before most other pending broadcasts.
+     */
+    public long DELAY_PERSISTENT_PROC_MILLIS = DEFAULT_DELAY_FOREGROUND_PROC_MILLIS;
+    private static final String KEY_DELAY_PERSISTENT_PROC_MILLIS =
+            "bcast_delay_persistent_proc_millis";
+    private static final long DEFAULT_DELAY_PERSISTENT_PROC_MILLIS = -120_000;
+
+    /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of complete
      * historical broadcasts to retain for debugging purposes.
      */
@@ -411,6 +431,10 @@
                     DEFAULT_DELAY_CACHED_MILLIS);
             DELAY_URGENT_MILLIS = getDeviceConfigLong(KEY_DELAY_URGENT_MILLIS,
                     DEFAULT_DELAY_URGENT_MILLIS);
+            DELAY_FOREGROUND_PROC_MILLIS = getDeviceConfigLong(KEY_DELAY_FOREGROUND_PROC_MILLIS,
+                    DEFAULT_DELAY_FOREGROUND_PROC_MILLIS);
+            DELAY_PERSISTENT_PROC_MILLIS = getDeviceConfigLong(KEY_DELAY_PERSISTENT_PROC_MILLIS,
+                    DEFAULT_DELAY_PERSISTENT_PROC_MILLIS);
             MAX_HISTORY_COMPLETE_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_COMPLETE_SIZE,
                     DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
             MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
@@ -463,6 +487,10 @@
                     TimeUtils.formatDuration(DELAY_CACHED_MILLIS)).println();
             pw.print(KEY_DELAY_URGENT_MILLIS,
                     TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
+            pw.print(KEY_DELAY_FOREGROUND_PROC_MILLIS,
+                    TimeUtils.formatDuration(DELAY_FOREGROUND_PROC_MILLIS)).println();
+            pw.print(KEY_DELAY_PERSISTENT_PROC_MILLIS,
+                    TimeUtils.formatDuration(DELAY_PERSISTENT_PROC_MILLIS)).println();
             pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
             pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
             pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 2803b4b..3ac2b2b 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1098,8 +1098,11 @@
                 mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
                 mRunnableAtReason = REASON_INSTRUMENTED;
             } else if (mUidForeground) {
-                mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+                mRunnableAt = runnableAt + constants.DELAY_FOREGROUND_PROC_MILLIS;
                 mRunnableAtReason = REASON_FOREGROUND;
+            } else if (mProcessPersistent) {
+                mRunnableAt = runnableAt + constants.DELAY_PERSISTENT_PROC_MILLIS;
+                mRunnableAtReason = REASON_PERSISTENT;
             } else if (mCountOrdered > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1112,9 +1115,6 @@
             } else if (mCountManifest > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_MANIFEST;
-            } else if (mProcessPersistent) {
-                mRunnableAt = runnableAt;
-                mRunnableAtReason = REASON_PERSISTENT;
             } else if (mUidCached) {
                 if (r.deferUntilActive) {
                     // All enqueued broadcasts are deferrable, defer
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index b22ece3..9e66bfe 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -175,7 +175,7 @@
     // while-in-use permissions in FGS started from background might be restricted.
     boolean mAllowWhileInUsePermissionInFgs;
     @PowerExemptionManager.ReasonCode
-    int mAllowWhileInUsePermissionInFgsReason;
+    int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
 
     // Integer version of mAllowWhileInUsePermissionInFgs that we keep track to compare
     // the old and new logics.
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 71401f4..f4c9d05 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -209,6 +209,7 @@
     private void init() {
         setupMessaging(mContext);
 
+        initAudioHalBluetoothState();
         initRoutingStrategyIds();
         mPreferredCommunicationDevice = null;
         updateActiveCommunicationDevice();
@@ -864,21 +865,174 @@
         }
     }
 
-    /**
-     * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
-     */
+    // Lock protecting state variable related to Bluetooth audio state
+    private final Object mBluetoothAudioStateLock = new Object();
+
+    // Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
+    @GuardedBy("mBluetoothAudioStateLock")
     private boolean mBluetoothScoOn;
+    // value of BT_SCO parameter currently applied to audio HAL.
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothScoOnApplied;
+
+    // A2DP suspend state requested by AudioManager.setA2dpSuspended() API.
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothA2dpSuspendedExt;
+    // A2DP suspend state requested by AudioDeviceInventory.
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothA2dpSuspendedInt;
+    // value of BT_A2dpSuspendedSCO parameter currently applied to audio HAL.
+
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothA2dpSuspendedApplied;
+
+    // LE Audio suspend state requested by AudioManager.setLeAudioSuspended() API.
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothLeSuspendedExt;
+    // LE Audio suspend state requested by AudioDeviceInventory.
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothLeSuspendedInt;
+    // value of LeAudioSuspended parameter currently applied to audio HAL.
+    @GuardedBy("mBluetoothAudioStateLock")
+    private boolean mBluetoothLeSuspendedApplied;
+
+    private void initAudioHalBluetoothState() {
+        synchronized (mBluetoothAudioStateLock) {
+            mBluetoothScoOnApplied = false;
+            AudioSystem.setParameters("BT_SCO=off");
+            mBluetoothA2dpSuspendedApplied = false;
+            AudioSystem.setParameters("A2dpSuspended=false");
+            mBluetoothLeSuspendedApplied = false;
+            AudioSystem.setParameters("LeAudioSuspended=false");
+        }
+    }
+
+    @GuardedBy("mBluetoothAudioStateLock")
+    private void updateAudioHalBluetoothState() {
+        if (mBluetoothScoOn != mBluetoothScoOnApplied) {
+            if (AudioService.DEBUG_COMM_RTE) {
+                Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothScoOn: "
+                        + mBluetoothScoOn + ", mBluetoothScoOnApplied: " + mBluetoothScoOnApplied);
+            }
+            if (mBluetoothScoOn) {
+                if (!mBluetoothA2dpSuspendedApplied) {
+                    AudioSystem.setParameters("A2dpSuspended=true");
+                    mBluetoothA2dpSuspendedApplied = true;
+                }
+                if (!mBluetoothLeSuspendedApplied) {
+                    AudioSystem.setParameters("LeAudioSuspended=true");
+                    mBluetoothLeSuspendedApplied = true;
+                }
+                AudioSystem.setParameters("BT_SCO=on");
+            } else {
+                AudioSystem.setParameters("BT_SCO=off");
+            }
+            mBluetoothScoOnApplied = mBluetoothScoOn;
+        }
+        if (!mBluetoothScoOnApplied) {
+            if ((mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt)
+                    != mBluetoothA2dpSuspendedApplied) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothA2dpSuspendedExt: "
+                            + mBluetoothA2dpSuspendedExt
+                            + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
+                            + ", mBluetoothA2dpSuspendedApplied: "
+                            + mBluetoothA2dpSuspendedApplied);
+                }
+                mBluetoothA2dpSuspendedApplied =
+                        mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt;
+                if (mBluetoothA2dpSuspendedApplied) {
+                    AudioSystem.setParameters("A2dpSuspended=true");
+                } else {
+                    AudioSystem.setParameters("A2dpSuspended=false");
+                }
+            }
+            if ((mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt)
+                    != mBluetoothLeSuspendedApplied) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothLeSuspendedExt: "
+                            + mBluetoothLeSuspendedExt
+                            + ", mBluetoothLeSuspendedInt: " + mBluetoothLeSuspendedInt
+                            + ", mBluetoothLeSuspendedApplied: " + mBluetoothLeSuspendedApplied);
+                }
+                mBluetoothLeSuspendedApplied =
+                        mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt;
+                if (mBluetoothLeSuspendedApplied) {
+                    AudioSystem.setParameters("LeAudioSuspended=true");
+                } else {
+                    AudioSystem.setParameters("LeAudioSuspended=false");
+                }
+            }
+        }
+    }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
         }
-        synchronized (mDeviceStateLock) {
+        synchronized (mBluetoothAudioStateLock) {
             mBluetoothScoOn = on;
+            updateAudioHalBluetoothState();
             postUpdateCommunicationRouteClient(eventSource);
         }
     }
 
+    /*package*/ void setA2dpSuspended(boolean enable, boolean internal, String eventSource) {
+        synchronized (mBluetoothAudioStateLock) {
+            if (AudioService.DEBUG_COMM_RTE) {
+                Log.v(TAG, "setA2dpSuspended source: " + eventSource + ", enable: "
+                        + enable + ", internal: " + internal
+                        + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
+                        + ", mBluetoothA2dpSuspendedExt: " + mBluetoothA2dpSuspendedExt);
+            }
+            if (internal) {
+                mBluetoothA2dpSuspendedInt = enable;
+            } else {
+                mBluetoothA2dpSuspendedExt = enable;
+            }
+            updateAudioHalBluetoothState();
+        }
+    }
+
+    /*package*/ void clearA2dpSuspended() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "clearA2dpSuspended");
+        }
+        synchronized (mBluetoothAudioStateLock) {
+            mBluetoothA2dpSuspendedInt = false;
+            mBluetoothA2dpSuspendedExt = false;
+            updateAudioHalBluetoothState();
+        }
+    }
+
+    /*package*/ void setLeAudioSuspended(boolean enable, boolean internal, String eventSource) {
+        synchronized (mBluetoothAudioStateLock) {
+            if (AudioService.DEBUG_COMM_RTE) {
+                Log.v(TAG, "setLeAudioSuspended source: " + eventSource + ", enable: "
+                        + enable + ", internal: " + internal
+                        + ", mBluetoothLeSuspendedInt: " + mBluetoothA2dpSuspendedInt
+                        + ", mBluetoothLeSuspendedExt: " + mBluetoothA2dpSuspendedExt);
+            }
+            if (internal) {
+                mBluetoothLeSuspendedInt = enable;
+            } else {
+                mBluetoothLeSuspendedExt = enable;
+            }
+            updateAudioHalBluetoothState();
+        }
+    }
+
+    /*package*/ void clearLeAudioSuspended() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "clearLeAudioSuspended");
+        }
+        synchronized (mBluetoothAudioStateLock) {
+            mBluetoothLeSuspendedInt = false;
+            mBluetoothLeSuspendedExt = false;
+            updateAudioHalBluetoothState();
+        }
+    }
+
     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.startWatchingRoutes(observer);
@@ -1985,7 +2139,11 @@
      */
     @GuardedBy("mDeviceStateLock")
     @Nullable private AudioDeviceAttributes preferredCommunicationDevice() {
-        boolean btSCoOn = mBluetoothScoOn && mBtHelper.isBluetoothScoOn();
+        boolean btSCoOn = mBtHelper.isBluetoothScoOn();
+        synchronized (mBluetoothAudioStateLock) {
+            btSCoOn = btSCoOn && mBluetoothScoOn;
+        }
+
         if (btSCoOn) {
             // Use the SCO device known to BtHelper so that it matches exactly
             // what has been communicated to audio policy manager. The device
@@ -2020,12 +2178,6 @@
                 "updateCommunicationRoute, preferredCommunicationDevice: "
                 + preferredCommunicationDevice + " eventSource: " + eventSource)));
 
-        if (preferredCommunicationDevice == null
-                || preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
-            AudioSystem.setParameters("BT_SCO=off");
-        } else {
-            AudioSystem.setParameters("BT_SCO=on");
-        }
         if (preferredCommunicationDevice == null) {
             AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
             if (defaultDevice != null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 58c7326..a561612 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1479,7 +1479,7 @@
         }
 
         // Reset A2DP suspend state each time a new sink is connected
-        mAudioSystem.setParameters("A2dpSuspended=false");
+        mDeviceBroker.clearA2dpSuspended();
 
         // The convention for head tracking sensors associated with A2DP devices is to
         // use a UUID derived from the MAC address as follows:
@@ -1752,7 +1752,8 @@
     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
         // prevent any activity on the A2DP audio output to avoid unwanted
         // reconnection of the sink.
-        mAudioSystem.setParameters("A2dpSuspended=true");
+        mDeviceBroker.setA2dpSuspended(
+                true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater");
         // retrieve DeviceInfo before removing device
         final String deviceKey =
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -1899,7 +1900,7 @@
                         "LE Audio device addr=" + address + " now available").printLog(TAG));
             }
             // Reset LEA suspend state each time a new sink is connected
-            mAudioSystem.setParameters("LeAudioSuspended=false");
+            mDeviceBroker.clearLeAudioSuspended();
 
             UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
@@ -1954,7 +1955,8 @@
     private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
         // prevent any activity on the LEA output to avoid unwanted
         // reconnection of the sink.
-        mAudioSystem.setParameters("LeAudioSuspended=true");
+        mDeviceBroker.setLeAudioSuspended(
+                true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater");
         // the device will be made unavailable later, so consider it disconnected right away
         mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
         // send the delayed message to make the device unavailable later
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b3fffbf..355981a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6424,6 +6424,26 @@
         mDeviceBroker.setBluetoothScoOn(on, eventSource);
     }
 
+    /** @see AudioManager#setA2dpSuspended(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
+    public void setA2dpSuspended(boolean enable) {
+        super.setA2dpSuspended_enforcePermission();
+        final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable)
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        mDeviceBroker.setA2dpSuspended(enable, false /*internal*/, eventSource);
+    }
+
+    /** @see AudioManager#setA2dpSuspended(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
+    public void setLeAudioSuspended(boolean enable) {
+        super.setLeAudioSuspended_enforcePermission();
+        final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable)
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        mDeviceBroker.setLeAudioSuspended(enable, false /*internal*/, eventSource);
+    }
+
     /** @see AudioManager#isBluetoothScoOn()
      * Note that it doesn't report internal state, but state seen by apps (which may have
      * called setBluetoothScoOn() */
@@ -11033,6 +11053,11 @@
         dumpAccessibilityServiceUids(pw);
         dumpAssistantServicesUids(pw);
 
+        pw.print("  supportsBluetoothVariableLatency=");
+        pw.println(AudioSystem.supportsBluetoothVariableLatency());
+        pw.print("  isBluetoothVariableLatencyEnabled=");
+        pw.println(AudioSystem.isBluetoothVariableLatencyEnabled());
+
         dumpAudioPolicies(pw);
         mDynPolicyLogger.dump(pw);
         mPlaybackMonitor.dump(pw);
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index bfa6c36e..e46c3cc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -445,8 +445,8 @@
     /*package*/ synchronized void resetBluetoothSco() {
         mScoAudioState = SCO_STATE_INACTIVE;
         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        AudioSystem.setParameters("A2dpSuspended=false");
-        AudioSystem.setParameters("LeAudioSuspended=false");
+        mDeviceBroker.clearA2dpSuspended();
+        mDeviceBroker.clearLeAudioSuspended();
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index e5c50e6..4edc8bc 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -23,7 +23,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerInternal;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.view.Display;
@@ -333,6 +332,10 @@
         return mPrimaryDisplayDevice != null;
     }
 
+    boolean isDirtyLocked() {
+        return mDirty;
+    }
+
     /**
      * Updates the {@link DisplayGroup} to which the logical display belongs.
      *
@@ -341,8 +344,7 @@
     public void updateDisplayGroupIdLocked(int groupId) {
         if (groupId != mDisplayGroupId) {
             mDisplayGroupId = groupId;
-            mBaseDisplayInfo.displayGroupId = groupId;
-            mInfo.set(null);
+            mDirty = true;
         }
     }
 
@@ -932,18 +934,6 @@
         return mDisplayGroupName;
     }
 
-    /**
-     * Returns whether a display group other than the default display group needs to be assigned.
-     *
-     * <p>If display group name is empty or {@code Display.FLAG_OWN_DISPLAY_GROUP} is set, the
-     * display is assigned to the default display group.
-     */
-    public boolean needsOwnDisplayGroupLocked() {
-        DisplayInfo info = getDisplayInfoLocked();
-        return (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0
-                || !TextUtils.isEmpty(mDisplayGroupName);
-    }
-
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
         pw.println("mIsEnabled=" + mIsEnabled);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 254441c2..d01b03f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -679,7 +679,9 @@
         for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {
             final int displayId = mLogicalDisplays.keyAt(i);
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
+            assignDisplayGroupLocked(display);
 
+            boolean wasDirty = display.isDirtyLocked();
             mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
             display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
 
@@ -713,19 +715,14 @@
             // The display is new.
             } else if (!wasPreviouslyUpdated) {
                 Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);
-                assignDisplayGroupLocked(display);
                 mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
 
             // Underlying displays device has changed to a different one.
             } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
-                // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
-                assignDisplayGroupLocked(display);
                 mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
 
             // Something about the display device has changed.
-            } else if (!mTempDisplayInfo.equals(newDisplayInfo)) {
-                // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
-                assignDisplayGroupLocked(display);
+            } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
                 // If only the hdr/sdr ratio changed, then send just the event for that case
                 if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
                     mLogicalDisplaysToUpdate.put(displayId,
@@ -851,9 +848,18 @@
         }
     }
 
+    /** This method should be called before LogicalDisplay.updateLocked,
+     * DisplayInfo in LogicalDisplay (display.getDisplayInfoLocked()) is not updated yet,
+     * and should not be used directly or indirectly in this method */
     private void assignDisplayGroupLocked(LogicalDisplay display) {
+        if (!display.isValidLocked()) { // null check for display.mPrimaryDisplayDevice
+            return;
+        }
+        // updated primary device directly from LogicalDisplay (not from DisplayInfo)
+        final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+        // final in LogicalDisplay
         final int displayId = display.getDisplayIdLocked();
-        final String primaryDisplayUniqueId = display.getPrimaryDisplayDeviceLocked().getUniqueId();
+        final String primaryDisplayUniqueId = displayDevice.getUniqueId();
         final Integer linkedDeviceUniqueId =
                 mVirtualDeviceDisplayMapping.get(primaryDisplayUniqueId);
 
@@ -866,8 +872,17 @@
         }
         final DisplayGroup oldGroup = getDisplayGroupLocked(groupId);
 
-        // Get the new display group if a change is needed
-        final boolean needsOwnDisplayGroup = display.needsOwnDisplayGroupLocked();
+        // groupName directly from LogicalDisplay (not from DisplayInfo)
+        final String groupName = display.getDisplayGroupNameLocked();
+        // DisplayDeviceInfo is safe to use, it is updated earlier
+        final DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+        // Get the new display group if a change is needed, if display group name is empty and
+        // {@code DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP} is not set, the display is assigned
+        // to the default display group.
+        final boolean needsOwnDisplayGroup =
+                (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0
+                        || !TextUtils.isEmpty(groupName);
+
         final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP;
         final boolean needsDeviceDisplayGroup =
                 !needsOwnDisplayGroup && linkedDeviceUniqueId != null;
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index 73ab782..712a696 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -23,6 +23,7 @@
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Handler;
@@ -48,6 +49,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.LongSupplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -62,15 +64,14 @@
     private static final String SUBSYSTEM_SENSOR_STRING = "Sensor";
     private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data";
     private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
-    @VisibleForTesting
-    static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+
     private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
 
     private final Handler mHandler;
     private final IrqDeviceMap mIrqDeviceMap;
     @VisibleForTesting
     final Config mConfig = new Config();
-    private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
+    private final WakingActivityHistory mRecentWakingActivity;
 
     @VisibleForTesting
     final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
@@ -85,6 +86,8 @@
 
     public CpuWakeupStats(Context context, int mapRes, Handler handler) {
         mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+        mRecentWakingActivity = new WakingActivityHistory(
+                () -> mConfig.WAKING_ACTIVITY_RETENTION_MS);
         mHandler = handler;
     }
 
@@ -202,15 +205,14 @@
     /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
     public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
             String rawReason) {
-        final Wakeup parsedWakeup = Wakeup.parseWakeup(rawReason, elapsedRealtime, uptime);
+        final Wakeup parsedWakeup = Wakeup.parseWakeup(rawReason, elapsedRealtime, uptime,
+                mIrqDeviceMap);
         if (parsedWakeup == null) {
+            // This wakeup is unsupported for attribution. Exit.
             return;
         }
         mWakeupEvents.put(elapsedRealtime, parsedWakeup);
         attemptAttributionFor(parsedWakeup);
-        // Assuming that wakeups always arrive in monotonically increasing elapsedRealtime order,
-        // we can delete all history that will not be useful in attributing future wakeups.
-        mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
 
         // Limit history of wakeups and their attribution to the last retentionDuration. Note that
         // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
@@ -244,25 +246,22 @@
     }
 
     private synchronized void attemptAttributionFor(Wakeup wakeup) {
-        final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
-        if (subsystems == null) {
-            // We don't support attribution for this kind of wakeup yet.
-            return;
-        }
+        final SparseBooleanArray subsystems = wakeup.mResponsibleSubsystems;
 
         SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
         if (attribution == null) {
             attribution = new SparseArray<>();
             mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
         }
+        final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;
 
         for (int subsystemIdx = 0; subsystemIdx < subsystems.size(); subsystemIdx++) {
             final int subsystem = subsystems.keyAt(subsystemIdx);
 
-            // Blame all activity that happened WAKEUP_REASON_HALF_WINDOW_MS before or after
+            // Blame all activity that happened matchingWindowMillis before or after
             // the wakeup from each responsible subsystem.
-            final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS;
-            final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS;
+            final long startTime = wakeup.mElapsedMillis - matchingWindowMillis;
+            final long endTime = wakeup.mElapsedMillis + matchingWindowMillis;
 
             final SparseIntArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
                     startTime, endTime);
@@ -272,18 +271,16 @@
 
     private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed,
             SparseIntArray uidProcStates) {
+        final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;
+
         final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
-                activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS);
+                activityElapsed - matchingWindowMillis);
         final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
-                activityElapsed + WAKEUP_REASON_HALF_WINDOW_MS);
+                activityElapsed + matchingWindowMillis);
 
         for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
             final Wakeup wakeup = mWakeupEvents.valueAt(wakeupIdx);
-            final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
-            if (subsystems == null) {
-                // Unsupported for attribution
-                continue;
-            }
+            final SparseBooleanArray subsystems = wakeup.mResponsibleSubsystems;
             if (subsystems.get(subsystem)) {
                 // We don't expect more than one wakeup to be found within such a short window, so
                 // just attribute this one and exit
@@ -405,11 +402,13 @@
      */
     @VisibleForTesting
     static final class WakingActivityHistory {
-        private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
-
+        private LongSupplier mRetentionSupplier;
         @VisibleForTesting
-        final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity =
-                new SparseArray<>();
+        final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>();
+
+        WakingActivityHistory(LongSupplier retentionSupplier) {
+            mRetentionSupplier = retentionSupplier;
+        }
 
         void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) {
             if (uidProcStates == null) {
@@ -433,27 +432,13 @@
                     }
                 }
             }
-            // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
-            // Note that the last activity is always present, even if it occurred before
-            // WAKING_ACTIVITY_RETENTION_MS.
+            // Limit activity history per subsystem to the last retention period as supplied by
+            // mRetentionSupplier. Note that the last activity is always present, even if it
+            // occurred before the retention period.
             final int endIdx = wakingActivity.closestIndexOnOrBefore(
-                    elapsedRealtime - WAKING_ACTIVITY_RETENTION_MS);
+                    elapsedRealtime - mRetentionSupplier.getAsLong());
             for (int i = endIdx; i >= 0; i--) {
-                wakingActivity.removeAt(endIdx);
-            }
-        }
-
-        void clearAllBefore(long elapsedRealtime) {
-            for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) {
-                final TimeSparseArray<SparseIntArray> activityPerSubsystem =
-                        mWakingActivity.valueAt(subsystemIdx);
-                final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime);
-                for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) {
-                    activityPerSubsystem.removeAt(removeIdx);
-                }
-                // Generally waking activity is a high frequency occurrence for any subsystem, so we
-                // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
-                // This will leave one TimeSparseArray per subsystem, which are few right now.
+                wakingActivity.removeAt(i);
             }
         }
 
@@ -515,33 +500,6 @@
         }
     }
 
-    private SparseBooleanArray getResponsibleSubsystemsForWakeup(Wakeup wakeup) {
-        if (ArrayUtils.isEmpty(wakeup.mDevices)) {
-            return null;
-        }
-        final SparseBooleanArray result = new SparseBooleanArray();
-        for (final Wakeup.IrqDevice device : wakeup.mDevices) {
-            final List<String> rawSubsystems = mIrqDeviceMap.getSubsystemsForDevice(device.mDevice);
-
-            boolean anyKnownSubsystem = false;
-            if (rawSubsystems != null) {
-                for (int i = 0; i < rawSubsystems.size(); i++) {
-                    final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i));
-                    if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) {
-                        // Just in case the xml had arbitrary subsystem names, we want to make sure
-                        // that we only put the known ones into our attribution map.
-                        result.put(subsystem, true);
-                        anyKnownSubsystem = true;
-                    }
-                }
-            }
-            if (!anyKnownSubsystem) {
-                result.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true);
-            }
-        }
-        return result;
-    }
-
     static int stringToKnownSubsystem(String rawSubsystem) {
         switch (rawSubsystem) {
             case SUBSYSTEM_ALARM_STRING:
@@ -598,15 +556,19 @@
         long mElapsedMillis;
         long mUptimeMillis;
         IrqDevice[] mDevices;
+        SparseBooleanArray mResponsibleSubsystems;
 
-        private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
+        private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis,
+                SparseBooleanArray responsibleSubsystems) {
             mType = type;
             mDevices = devices;
             mElapsedMillis = elapsedMillis;
             mUptimeMillis = uptimeMillis;
+            mResponsibleSubsystems = responsibleSubsystems;
         }
 
-        static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
+        static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis,
+                IrqDeviceMap deviceMap) {
             final String[] components = rawReason.split(":");
             if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) {
                 // Accounting of aborts is not supported yet.
@@ -616,6 +578,7 @@
             int type = TYPE_IRQ;
             int parsedDeviceCount = 0;
             final IrqDevice[] parsedDevices = new IrqDevice[components.length];
+            final SparseBooleanArray responsibleSubsystems = new SparseBooleanArray();
 
             for (String component : components) {
                 final Matcher matcher = sIrqPattern.matcher(component.trim());
@@ -635,13 +598,35 @@
                         continue;
                     }
                     parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device);
+
+                    final List<String> rawSubsystems = deviceMap.getSubsystemsForDevice(device);
+                    boolean anyKnownSubsystem = false;
+                    if (rawSubsystems != null) {
+                        for (int i = 0; i < rawSubsystems.size(); i++) {
+                            final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i));
+                            if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) {
+                                // Just in case the xml had arbitrary subsystem names, we want to
+                                // make sure that we only put the known ones into our map.
+                                responsibleSubsystems.put(subsystem, true);
+                                anyKnownSubsystem = true;
+                            }
+                        }
+                    }
+                    if (!anyKnownSubsystem) {
+                        responsibleSubsystems.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true);
+                    }
                 }
             }
             if (parsedDeviceCount == 0) {
                 return null;
             }
+            if (responsibleSubsystems.size() == 1 && responsibleSubsystems.get(
+                    CPU_WAKEUP_SUBSYSTEM_UNKNOWN, false)) {
+                // There is no attributable subsystem here, so we do not support it.
+                return null;
+            }
             return new Wakeup(type, Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
-                    uptimeMillis);
+                    uptimeMillis, responsibleSubsystems);
         }
 
         @Override
@@ -651,6 +636,7 @@
                     + ", mElapsedMillis=" + mElapsedMillis
                     + ", mUptimeMillis=" + mUptimeMillis
                     + ", mDevices=" + Arrays.toString(mDevices)
+                    + ", mResponsibleSubsystems=" + mResponsibleSubsystems
                     + '}';
         }
 
@@ -672,18 +658,28 @@
 
     static final class Config implements DeviceConfig.OnPropertiesChangedListener {
         static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms";
+        static final String KEY_WAKEUP_MATCHING_WINDOW_MS = "wakeup_matching_window_ms";
+        static final String KEY_WAKING_ACTIVITY_RETENTION_MS = "waking_activity_retention_ms";
 
         private static final String[] PROPERTY_NAMES = {
                 KEY_WAKEUP_STATS_RETENTION_MS,
+                KEY_WAKEUP_MATCHING_WINDOW_MS,
+                KEY_WAKING_ACTIVITY_RETENTION_MS,
         };
 
         static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3);
+        private static final long DEFAULT_WAKEUP_MATCHING_WINDOW_MS = TimeUnit.SECONDS.toMillis(1);
+        private static final long DEFAULT_WAKING_ACTIVITY_RETENTION_MS =
+                TimeUnit.MINUTES.toMillis(5);
 
         /**
          * Wakeup stats are retained only for this duration.
          */
         public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS;
+        public volatile long WAKEUP_MATCHING_WINDOW_MS = DEFAULT_WAKEUP_MATCHING_WINDOW_MS;
+        public volatile long WAKING_ACTIVITY_RETENTION_MS = DEFAULT_WAKING_ACTIVITY_RETENTION_MS;
 
+        @SuppressLint("MissingPermission")
         void register(Executor executor) {
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
                     executor, this);
@@ -702,6 +698,15 @@
                         WAKEUP_STATS_RETENTION_MS = properties.getLong(
                                 KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS);
                         break;
+                    case KEY_WAKEUP_MATCHING_WINDOW_MS:
+                        WAKEUP_MATCHING_WINDOW_MS = properties.getLong(
+                                KEY_WAKEUP_MATCHING_WINDOW_MS, DEFAULT_WAKEUP_MATCHING_WINDOW_MS);
+                        break;
+                    case KEY_WAKING_ACTIVITY_RETENTION_MS:
+                        WAKING_ACTIVITY_RETENTION_MS = properties.getLong(
+                                KEY_WAKING_ACTIVITY_RETENTION_MS,
+                                DEFAULT_WAKING_ACTIVITY_RETENTION_MS);
+                        break;
                 }
             }
         }
@@ -711,7 +716,19 @@
 
             pw.increaseIndent();
 
-            pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS);
+            pw.print(KEY_WAKEUP_STATS_RETENTION_MS);
+            pw.print("=");
+            TimeUtils.formatDuration(WAKEUP_STATS_RETENTION_MS, pw);
+            pw.println();
+
+            pw.print(KEY_WAKEUP_MATCHING_WINDOW_MS);
+            pw.print("=");
+            TimeUtils.formatDuration(WAKEUP_MATCHING_WINDOW_MS, pw);
+            pw.println();
+
+            pw.print(KEY_WAKING_ACTIVITY_RETENTION_MS);
+            pw.print("=");
+            TimeUtils.formatDuration(WAKING_ACTIVITY_RETENTION_MS, pw);
             pw.println();
 
             pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
index c05d148..87ebdbf 100644
--- a/services/core/java/com/android/server/tv/TvInputHal.java
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -234,6 +234,7 @@
                 int type = msg.arg2;
                 Bundle data = (Bundle) msg.obj;
                 mCallback.onTvMessage(deviceId, type, data);
+                break;
             }
 
             default:
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7a43728..9add537 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3987,6 +3987,8 @@
             if (mFallbackWallpaper != null) {
                 dumpWallpaper(mFallbackWallpaper, pw);
             }
+            pw.print("mIsLockscreenLiveWallpaperEnabled=");
+            pw.println(mIsLockscreenLiveWallpaperEnabled);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 778951a..98ee98b 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -248,7 +248,10 @@
                     Slog.e(TAG, "WM sent Transaction to organized, but never received" +
                            " commit callback. Application ANR likely to follow.");
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                    onCommitted(merged);
+                    synchronized (mWm.mGlobalLock) {
+                        onCommitted(merged.mNativeObject != 0
+                                ? merged : mWm.mTransactionFactory.get());
+                    }
                 }
             };
             CommitCallback callback = new CommitCallback();
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index 040da88..4da55e2 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -98,6 +98,13 @@
                     mSession == null ? null : mSession.getVirtualDisplayId());
             incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
                     incomingSession.getVirtualDisplayId());
+            if (incomingDisplayContent == null) {
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Incoming session on display %d can't be set since it "
+                                + "is already null; the corresponding VirtualDisplay must have "
+                                + "already been removed.", incomingSession.getVirtualDisplayId());
+                return;
+            }
             incomingDisplayContent.setContentRecordingSession(incomingSession);
             // TODO(b/270118861): When user grants consent to re-use, explicitly ask ContentRecorder
             //  to update, since no config/display change arrives. Mark recording as black.
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index 72829c0..5f95c28 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -99,7 +99,10 @@
     // Extract unique HDR output types.
     std::set<int> hdrOutputTypes;
     for (const auto& hdrConversionCapability : hdrConversionCapabilities) {
-        hdrOutputTypes.insert(hdrConversionCapability.outputType);
+        // Filter out the value for SDR which is 0.
+        if (hdrConversionCapability.outputType > 0) {
+            hdrOutputTypes.insert(hdrConversionCapability.outputType);
+        }
     }
     jintArray array = env->NewIntArray(hdrOutputTypes.size());
     if (array == nullptr) {
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index a3eb390..6782b5c 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -16,6 +16,8 @@
 
 #include "JTvInputHal.h"
 
+#include <nativehelper/ScopedLocalRef.h>
+
 namespace android {
 
 JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
@@ -278,21 +280,27 @@
 void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type,
                               AidlTvMessage& message, signed char data[], int dataLength) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jobject bundle = env->NewObject(gBundleClassInfo.clazz, gBundleClassInfo.constructor);
-    const jsize len = static_cast<jsize>(dataLength);
-    jbyteArray convertedData = env->NewByteArray(len);
-    env->SetByteArrayRegion(convertedData, 0, len, reinterpret_cast<const jbyte*>(data));
-    env->CallObjectMethod(bundle, gBundleClassInfo.putString,
-                          "android.media.tv.TvInputManager.subtype", message.subType.c_str());
-    env->CallObjectMethod(bundle, gBundleClassInfo.putByteArray,
-                          "android.media.tv.TvInputManager.raw_data", convertedData);
-    env->CallObjectMethod(bundle, gBundleClassInfo.putInt,
-                          "android.media.tv.TvInputManager.group_id", message.groupId);
-    env->CallObjectMethod(bundle, gBundleClassInfo.putInt,
-                          "android.media.tv.TvInputManager.stream_id", streamId);
+    ScopedLocalRef<jobject> bundle(env,
+                                   env->NewObject(gBundleClassInfo.clazz,
+                                                  gBundleClassInfo.constructor));
+    ScopedLocalRef<jbyteArray> convertedData(env, env->NewByteArray(dataLength));
+    env->SetByteArrayRegion(convertedData.get(), 0, dataLength, reinterpret_cast<jbyte*>(data));
+    std::string key = "android.media.tv.TvInputManager.raw_data";
+    ScopedLocalRef<jstring> jkey(env, env->NewStringUTF(key.c_str()));
+    env->CallVoidMethod(bundle.get(), gBundleClassInfo.putByteArray, jkey.get(),
+                        convertedData.get());
+    ScopedLocalRef<jstring> subtype(env, env->NewStringUTF(message.subType.c_str()));
+    key = "android.media.tv.TvInputManager.subtype";
+    jkey = ScopedLocalRef<jstring>(env, env->NewStringUTF(key.c_str()));
+    env->CallVoidMethod(bundle.get(), gBundleClassInfo.putString, jkey.get(), subtype.get());
+    key = "android.media.tv.TvInputManager.group_id";
+    jkey = ScopedLocalRef<jstring>(env, env->NewStringUTF(key.c_str()));
+    env->CallVoidMethod(bundle.get(), gBundleClassInfo.putInt, jkey.get(), message.groupId);
+    key = "android.media.tv.TvInputManager.stream_id";
+    jkey = ScopedLocalRef<jstring>(env, env->NewStringUTF(key.c_str()));
+    env->CallVoidMethod(bundle.get(), gBundleClassInfo.putInt, jkey.get(), streamId);
     env->CallVoidMethod(mThiz, gTvInputHalClassInfo.tvMessageReceived, deviceId,
-                        static_cast<int>(type), bundle);
-    env->DeleteLocalRef(convertedData);
+                        static_cast<jint>(type), bundle.get());
 }
 
 void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index d9467a5..c7a71ee 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -39,7 +39,7 @@
         "PackageManagerServiceHostTestsIntentVerifyUtils",
         "block_device_writer_jar",
     ],
-    test_suites: ["general-tests"],
+    test_suites: ["device-tests"],
     data: [
         ":PackageManagerTestApex",
         ":PackageManagerTestApexApp",
diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
index 2382548..f594f6f 100644
--- a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
+++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
@@ -30,6 +30,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
+        <option name="install-arg" value="-g" />
         <option name="test-file-name" value="PackageManagerServiceServerTests.apk" />
     </target_preparer>
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 581fe4a..f47954b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -537,6 +537,30 @@
         assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
     }
 
+    @Test
+    public void testRunnableAt_persistentProc() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver()));
+        enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
+
+        assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+
+        doReturn(true).when(mProcess).isPersistent();
+        queue.setProcessAndUidState(mProcess, false, false);
+        assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_PERSISTENT, queue.getRunnableAtReason());
+
+        doReturn(false).when(mProcess).isPersistent();
+        queue.setProcessAndUidState(mProcess, false, false);
+        assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+    }
+
     /**
      * Verify that a cached process that would normally be delayed becomes
      * immediately runnable when the given broadcast is enqueued.
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index f6cf571..30ff8ba 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
@@ -191,6 +192,19 @@
     }
 
     @Test
+    public void testUpdateLayoutLimitedRefreshRate_setsDirtyFlag() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
     public void testUpdateRefreshRateThermalThrottling() {
         SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
         refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
@@ -207,6 +221,45 @@
     }
 
     @Test
+    public void testUpdateRefreshRateThermalThrottling_setsDirtyFlag() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testUpdateDisplayGroupIdLocked() {
+        int newId = 999;
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateDisplayGroupIdLocked(newId);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(newId, info3.displayGroupId);
+    }
+
+    @Test
+    public void testUpdateDisplayGroupIdLocked_setsDirtyFlag() {
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateDisplayGroupIdLocked(99);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
     public void testSetThermalBrightnessThrottlingDataId() {
         String brightnessThrottlingDataId = "throttling_data_id";
         DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
@@ -220,4 +273,15 @@
         assertNotEquals(info3, info2);
         assertEquals(brightnessThrottlingDataId, info3.thermalBrightnessThrottlingDataId);
     }
+
+    @Test
+    public void testSetThermalBrightnessThrottlingDataId_setsDirtyFlag() {
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked("99");
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index 76b6a82..d181419 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -23,8 +23,6 @@
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
-import static com.android.server.power.stats.wakeups.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -55,7 +53,7 @@
     private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device";
     private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device";
     private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
-    private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
+    private static final String KERNEL_REASON_UNKNOWN_FORMAT = "free-form-reason test.alarm.device";
     private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
     private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device";
 
@@ -85,30 +83,29 @@
 
     @Test
     public void removesOldWakeups() {
-        // The xml resource doesn't matter for this test.
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS;
 
         final Set<Long> timestamps = new HashSet<>();
         final long firstWakeup = 453192;
 
-        obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ);
+        // Reasons do not matter for this test, as long as they map to known subsystems
+        obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_ALARM_IRQ);
         timestamps.add(firstWakeup);
         for (int i = 1; i < 1000; i++) {
             final long delta = mRandom.nextLong(retention);
             if (timestamps.add(firstWakeup + delta)) {
-                obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ);
+                obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_SENSOR_IRQ);
             }
         }
         assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
 
-        obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231,
-                KERNEL_REASON_UNKNOWN_IRQ);
+        obj.noteWakeupTimeAndReason(firstWakeup + retention, 231, KERNEL_REASON_WIFI_IRQ);
         assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
 
         for (int i = 0; i < 100; i++) {
             final long now = mRandom.nextLong(retention + 1, 100 * retention);
-            obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ);
+            obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_SOUND_TRIGGER_IRQ);
             assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0);
         }
     }
@@ -201,9 +198,9 @@
 
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5);
 
@@ -234,9 +231,9 @@
 
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, wakeupTime + 5, TEST_UID_3,
                 TEST_UID_5);
@@ -268,9 +265,9 @@
 
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 3, TEST_UID_4, TEST_UID_5);
 
@@ -300,9 +297,9 @@
         // Alarm activity
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
@@ -311,9 +308,9 @@
         // Wifi activity
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_4);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_3);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_3);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 2, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_2,
@@ -347,33 +344,67 @@
     }
 
     @Test
-    public void unknownIrqAttribution() {
+    public void unknownIrqSoloIgnored() {
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 92123410;
 
         obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
 
-        assertThat(obj.mWakeupEvents.size()).isEqualTo(1);
+        assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
 
-        // Unrelated subsystems, should not be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 3, TEST_UID_4,
                 TEST_UID_5);
 
-        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
-        assertThat(attribution).isNotNull();
-        assertThat(attribution.size()).isEqualTo(1);
-        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
-        final SparseIntArray uidProcStates = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
-        assertThat(uidProcStates == null || uidProcStates.size() == 0).isTrue();
+        // Any nearby activity should not end up in the attribution map.
+        assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
     }
 
     @Test
-    public void unknownWakeupIgnored() {
+    public void unknownAndWifiAttribution() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+        final long wakeupTime = 92123410;
+
+        populateDefaultProcStates(obj);
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 24,
+                KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_WIFI_IRQ);
+
+        // Wifi activity
+        // Outside the window, so should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4);
+        // Should be attributed
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 2, TEST_UID_1);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_3,
+                TEST_UID_5);
+
+        // Unrelated, should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution).isNotNull();
+        assertThat(attribution.size()).isEqualTo(2);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(
+                TEST_PROC_STATE_1);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_2)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(
+                TEST_PROC_STATE_3);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_4)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isNull();
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isFalse();
+    }
+
+    @Test
+    public void unknownFormatWakeupIgnored() {
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 72123210;
 
-        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
+        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN_FORMAT);
 
         // Should be ignored as this type of wakeup is not known.
         assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
index cba7dbe..b02618e 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
@@ -28,8 +28,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.ThreadLocalRandom;
+
 @RunWith(AndroidJUnit4.class)
 public class WakingActivityHistoryTest {
+    private volatile long mTestRetention = 54;
 
     private static boolean areSame(SparseIntArray a, SparseIntArray b) {
         if (a == b) {
@@ -55,7 +58,7 @@
 
     @Test
     public void recordActivityAppendsUids() {
-        final WakingActivityHistory history = new WakingActivityHistory();
+        final WakingActivityHistory history = new WakingActivityHistory(() -> Long.MAX_VALUE);
         final int subsystem = 42;
         final long timestamp = 54;
 
@@ -100,7 +103,7 @@
 
     @Test
     public void recordActivityDoesNotDeleteExistingUids() {
-        final WakingActivityHistory history = new WakingActivityHistory();
+        final WakingActivityHistory history = new WakingActivityHistory(() -> Long.MAX_VALUE);
         final int subsystem = 42;
         long timestamp = 101;
 
@@ -151,4 +154,120 @@
         assertThat(recordedUids.get(62, -1)).isEqualTo(31);
         assertThat(recordedUids.get(85, -1)).isEqualTo(39);
     }
+
+    @Test
+    public void removeBetween() {
+        final WakingActivityHistory history = new WakingActivityHistory(() -> Long.MAX_VALUE);
+
+        final int subsystem = 43;
+
+        final SparseIntArray uids = new SparseIntArray();
+        uids.put(1, 17);
+        uids.put(15, 2);
+        uids.put(62, 31);
+        history.recordActivity(subsystem, 123, uids);
+
+        uids.put(54, 91);
+        history.recordActivity(subsystem, 150, uids);
+
+        uids.put(101, 32);
+        uids.delete(1);
+        history.recordActivity(subsystem, 191, uids);
+
+        SparseIntArray removedUids = history.removeBetween(subsystem, 100, 122);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 124, 149);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 151, 190);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 192, 240);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+
+        // Removing from a different subsystem should do nothing.
+        removedUids = history.removeBetween(subsystem + 1, 0, 300);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 0, 300);
+        assertThat(removedUids.size()).isEqualTo(5);
+        assertThat(removedUids.get(1, -1)).isEqualTo(17);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(0);
+
+        history.recordActivity(subsystem, 23, uids);
+        uids.put(31, 123);
+        history.recordActivity(subsystem, 49, uids);
+        uids.put(177, 432);
+        history.recordActivity(subsystem, 89, uids);
+
+        removedUids = history.removeBetween(subsystem, 23, 23);
+        assertThat(removedUids.size()).isEqualTo(4);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(2);
+
+        removedUids = history.removeBetween(subsystem, 49, 54);
+        assertThat(removedUids.size()).isEqualTo(5);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+        assertThat(removedUids.get(31, -1)).isEqualTo(123);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(1);
+
+        removedUids = history.removeBetween(subsystem, 23, 89);
+        assertThat(removedUids.size()).isEqualTo(6);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+        assertThat(removedUids.get(31, -1)).isEqualTo(123);
+        assertThat(removedUids.get(177, -1)).isEqualTo(432);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void deletesActivityPastRetention() {
+        final WakingActivityHistory history = new WakingActivityHistory(() -> mTestRetention);
+        final int subsystem = 49;
+
+        mTestRetention = 454;
+
+        final long firstTime = 342;
+        for (int i = 0; i < mTestRetention; i++) {
+            history.recordActivity(subsystem, firstTime + i, new SparseIntArray());
+        }
+        assertThat(history.mWakingActivity.get(subsystem)).isNotNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(mTestRetention);
+
+        history.recordActivity(subsystem, firstTime + mTestRetention + 7, new SparseIntArray());
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(mTestRetention - 7);
+
+        final ThreadLocalRandom random = ThreadLocalRandom.current();
+
+        for (int i = 0; i < 100; i++) {
+            final long time = random.nextLong(firstTime + mTestRetention + 100,
+                    456 * mTestRetention);
+            history.recordActivity(subsystem, time, new SparseIntArray());
+            assertThat(history.mWakingActivity.get(subsystem).closestIndexOnOrBefore(
+                    time - mTestRetention)).isLessThan(0);
+        }
+    }
 }
diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
index 7d9a6a5..d16e90e 100644
--- a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
+++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
@@ -37,9 +37,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
@@ -54,7 +54,7 @@
     private LocaleStore.LocaleInfo mAppCurrentLocale;
     private Set<LocaleInfo> mAllAppActiveLocales;
     private Set<LocaleInfo> mImeLocales;
-    private List<LocaleInfo> mSystemCurrentLocales;
+    private Set<LocaleInfo> mSystemCurrentLocales;
     private Set<LocaleInfo> mSystemSupportedLocales;
     private AppLocaleStore.AppLocaleResult mResult;
     private static final String PKG1 = "pkg1";
@@ -73,14 +73,6 @@
     public void setUp() throws Exception {
         mAppLocaleCollector = spy(
                 new AppLocaleCollector(InstrumentationRegistry.getContext(), PKG1));
-
-        mAppCurrentLocale = createLocaleInfo("en-US", CURRENT);
-        mAllAppActiveLocales = initAllAppActivatedLocales();
-        mImeLocales = initImeLocales();
-        mSystemSupportedLocales = initSystemSupportedLocales();
-        mSystemCurrentLocales = initSystemCurrentLocales();
-        mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG,
-                initAppSupportedLocale());
     }
 
     @Test
@@ -88,7 +80,7 @@
         LocaleList.setDefault(
                 LocaleList.forLanguageTags("en-US-u-mu-fahrenhe,ar-JO-u-mu-fahrenhe-nu-latn"));
 
-        List<LocaleStore.LocaleInfo> list =
+        Set<LocaleStore.LocaleInfo> list =
                 mAppLocaleCollector.getSystemCurrentLocales();
 
         LocaleList expected = LocaleList.forLanguageTags("en-US,ar-JO-u-nu-latn");
@@ -99,7 +91,69 @@
     }
 
     @Test
-    public void testGetSupportedLocaleList() {
+    public void testGetSupportedLocaleList_filterNonAppsupportedSystemLanguage() {
+        mAppCurrentLocale = createLocaleInfo("en-US", CURRENT);
+
+        // App supports five locales
+        HashSet<Locale> appSupported =
+                getAppSupportedLocales(new String[] {
+                        "en-US",
+                        "fr",
+                        "ar",
+                        "es",
+                        "bn"
+                });
+        // There are six locales in system current locales.
+        mSystemCurrentLocales = getSystemCurrentLocales(new String[] {
+                "en-US",
+                "fr-FR",
+                "ar-JO",
+                "ca-AD",
+                "da-DK",
+                "es-US"
+        });
+        mAllAppActiveLocales = Collections.emptySet();
+        mImeLocales = Collections.emptySet();
+        mSystemSupportedLocales = Collections.emptySet();
+        mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG,
+                appSupported);
+
+        doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale();
+        doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales();
+        doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales();
+        doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales();
+        doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale(
+                anyObject(), eq(null), eq(true));
+        doReturn(mSystemCurrentLocales).when(
+                mAppLocaleCollector).getSystemCurrentLocales();
+
+        Set<LocaleInfo> result = mAppLocaleCollector.getSupportedLocaleList(null, true, false);
+
+        // The result would show four rather than six locales in the suggested region.
+        HashMap<String, Integer> expectedResult = new HashMap<>();
+        expectedResult.put("en-US", CURRENT); // The locale current App activates.
+        expectedResult.put("ar-JO", SYSTEM_AVAILABLE);
+        expectedResult.put("fr-FR", SYSTEM_AVAILABLE);
+        expectedResult.put("es-US", SYSTEM_AVAILABLE);
+        expectedResult.put(createLocaleInfo("", SYSTEM).getId(), SYSTEM); // System language title
+
+        assertEquals(result.size(), expectedResult.size());
+        for (LocaleStore.LocaleInfo info: result) {
+            int suggestionFlags = expectedResult.getOrDefault(info.getId(), -1);
+            assertEquals(info.mSuggestionFlags, suggestionFlags);
+        }
+    }
+
+    @Test
+    public void testGetSupportedLocaleList_withActiveLocalesFromOtherAppAndIme() {
+        mAppCurrentLocale = createLocaleInfo("en-US", CURRENT);
+        mAllAppActiveLocales = initAllAppActivatedLocales();
+        mImeLocales = initImeLocales();
+        mSystemSupportedLocales = initSystemSupportedLocales();
+        mSystemCurrentLocales = initSystemCurrentLocales();
+        mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG,
+                initAppSupportedLocale());
+
         doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale();
         doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales();
         doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales();
@@ -147,8 +201,8 @@
         );
     }
 
-    private List<LocaleInfo> initSystemCurrentLocales() {
-        return List.of(createLocaleInfo("zh-Hant-TW", SYSTEM_AVAILABLE),
+    private Set<LocaleInfo> initSystemCurrentLocales() {
+        return Set.of(createLocaleInfo("zh-Hant-TW", SYSTEM_AVAILABLE),
                 createLocaleInfo("ja-JP", SYSTEM_AVAILABLE),
                 // will be filtered because current App activates this locale.
                 createLocaleInfo("en-US", SYSTEM_AVAILABLE));
@@ -188,6 +242,22 @@
         return hs;
     }
 
+    private Set<LocaleStore.LocaleInfo> getSystemCurrentLocales(String []languageTags) {
+        HashSet<LocaleStore.LocaleInfo> hs = new HashSet<>(languageTags.length);
+        for (String tag:languageTags) {
+            hs.add(createLocaleInfo(tag, SYSTEM_AVAILABLE));
+        }
+        return hs;
+    }
+
+    private HashSet<Locale> getAppSupportedLocales(String []languageTags) {
+        HashSet<Locale> hs = new HashSet<>(languageTags.length);
+        for (String language:languageTags) {
+            hs.add(Locale.forLanguageTag(language));
+        }
+        return hs;
+    }
+
     private LocaleInfo createLocaleInfo(String languageTag, int suggestionFlag) {
         LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag(languageTag));
         localeInfo.mSuggestionFlags = suggestionFlag;