Merge changes I339886d1,Id53bfc8c into udc-dev

* changes:
  Fix themes of the bubble manage button
  Revert "Use ComponentCallbacks instead of ConfigurationChangeListener"
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 8e9f474..17076bc 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -98,6 +98,15 @@
     public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
 
     /**
+     * Delay freezing the app when the broadcast is delivered. This flag is not required if
+     * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED or
+     * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED are specified, as those will
+     * already defer freezing during the allowlist duration.
+     * @hide temporarily until the next release
+     */
+    public static final int TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED = 1 << 2;
+
+    /**
      * The list of temp allow list types.
      * @hide
      */
@@ -105,6 +114,7 @@
             TEMPORARY_ALLOW_LIST_TYPE_NONE,
             TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
             TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+            TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TempAllowListType {}
@@ -216,6 +226,11 @@
      * Set temp-allow-list for transferring accounts between users.
      */
     public static final int REASON_ACCOUNT_TRANSFER = 104;
+    /**
+     * Set temp-allow-list for server push messaging that can be deferred.
+     * @hide temporarily until the next release
+     */
+    public static final int REASON_PUSH_MESSAGING_DEFERRABLE = 105;
 
     /* Reason code range 200-299 are reserved for broadcast actions */
     /**
@@ -449,6 +464,7 @@
             REASON_PUSH_MESSAGING_OVER_QUOTA,
             REASON_ACTIVITY_RECOGNITION,
             REASON_ACCOUNT_TRANSFER,
+            REASON_PUSH_MESSAGING_DEFERRABLE,
             REASON_BOOT_COMPLETED,
             REASON_PRE_BOOT_COMPLETED,
             REASON_LOCKED_BOOT_COMPLETED,
@@ -781,6 +797,8 @@
                 return "ACTIVITY_RECOGNITION";
             case REASON_ACCOUNT_TRANSFER:
                 return "REASON_ACCOUNT_TRANSFER";
+            case REASON_PUSH_MESSAGING_DEFERRABLE:
+                return "PUSH_MESSAGING_DEFERRABLE";
             case REASON_BOOT_COMPLETED:
                 return "BOOT_COMPLETED";
             case REASON_PRE_BOOT_COMPLETED:
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 3f552b6..ea7460d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2600,34 +2600,43 @@
         } else {
             numSystemStops++;
         }
-        final int backoffAttempts = Math.max(1,
-                numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO);
-        long delayMillis;
+        final int backoffAttempts =
+                numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO;
+        final long earliestRuntimeMs;
 
-        switch (job.getBackoffPolicy()) {
-            case JobInfo.BACKOFF_POLICY_LINEAR: {
-                long backoff = initialBackoffMillis;
-                if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
-                    backoff = mConstants.MIN_LINEAR_BACKOFF_TIME_MS;
+        if (backoffAttempts == 0) {
+            earliestRuntimeMs = JobStatus.NO_EARLIEST_RUNTIME;
+        } else {
+            long delayMillis;
+            switch (job.getBackoffPolicy()) {
+                case JobInfo.BACKOFF_POLICY_LINEAR: {
+                    long backoff = initialBackoffMillis;
+                    if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
+                        backoff = mConstants.MIN_LINEAR_BACKOFF_TIME_MS;
+                    }
+                    delayMillis = backoff * backoffAttempts;
                 }
-                delayMillis = backoff * backoffAttempts;
-            } break;
-            default:
-                if (DEBUG) {
-                    Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
+                break;
+                default:
+                    if (DEBUG) {
+                        Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
+                    }
+                    // Intentional fallthrough.
+                case JobInfo.BACKOFF_POLICY_EXPONENTIAL: {
+                    long backoff = initialBackoffMillis;
+                    if (backoff < mConstants.MIN_EXP_BACKOFF_TIME_MS) {
+                        backoff = mConstants.MIN_EXP_BACKOFF_TIME_MS;
+                    }
+                    delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1);
                 }
-            case JobInfo.BACKOFF_POLICY_EXPONENTIAL: {
-                long backoff = initialBackoffMillis;
-                if (backoff < mConstants.MIN_EXP_BACKOFF_TIME_MS) {
-                    backoff = mConstants.MIN_EXP_BACKOFF_TIME_MS;
-                }
-                delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1);
-            } break;
+                break;
+            }
+            delayMillis =
+                    Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
+            earliestRuntimeMs = elapsedNowMillis + delayMillis;
         }
-        delayMillis =
-                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
         JobStatus newJob = new JobStatus(failureToReschedule,
-                elapsedNowMillis + delayMillis,
+                earliestRuntimeMs,
                 JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
                 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(),
                 failureToReschedule.getCumulativeExecutionTimeMs());
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/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index d85b2cd..0e78275 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager.EnforcingUser;
 
 import java.util.List;
 import java.util.Set;
@@ -326,4 +327,10 @@
      */
     public abstract List<Bundle> getApplicationRestrictionsPerAdminForUser(
             String packageName, @UserIdInt int userId);
+
+    /**
+     *  Returns a list of users who set a user restriction on a given user.
+     */
+    public abstract List<EnforcingUser> getUserRestrictionSources(String restriction,
+                @UserIdInt int userId);
 }
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/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 230f511..7f0a666 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2231,7 +2231,7 @@
 
             mDestroyed = true;
 
-            if (mIWallpaperEngine.mDisplayManager != null) {
+            if (mIWallpaperEngine != null && mIWallpaperEngine.mDisplayManager != null) {
                 mIWallpaperEngine.mDisplayManager.unregisterDisplayListener(mDisplayListener);
             }
 
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index fdecb8b..a89f795 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -87,7 +87,7 @@
      *
      * <p>Only set on the server side to sanitize any input from the client process.
      */
-    private boolean mWaitingToRecord = false;
+    private boolean mWaitingForConsent = false;
 
     /**
      * Default instance, with recording the display.
@@ -181,7 +181,7 @@
             @RecordContent int contentToRecord,
             int displayToRecord,
             @Nullable IBinder tokenToRecord,
-            boolean waitingToRecord) {
+            boolean waitingForConsent) {
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
 
@@ -195,7 +195,7 @@
 
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
-        this.mWaitingToRecord = waitingToRecord;
+        this.mWaitingForConsent = waitingForConsent;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -246,8 +246,8 @@
      * <p>Only set on the server side to sanitize any input from the client process.
      */
     @DataClass.Generated.Member
-    public boolean isWaitingToRecord() {
-        return mWaitingToRecord;
+    public boolean isWaitingForConsent() {
+        return mWaitingForConsent;
     }
 
     /**
@@ -309,8 +309,8 @@
      * <p>Only set on the server side to sanitize any input from the client process.
      */
     @DataClass.Generated.Member
-    public @NonNull ContentRecordingSession setWaitingToRecord( boolean value) {
-        mWaitingToRecord = value;
+    public @NonNull ContentRecordingSession setWaitingForConsent( boolean value) {
+        mWaitingForConsent = value;
         return this;
     }
 
@@ -325,7 +325,7 @@
                 "contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
                 "displayToRecord = " + mDisplayToRecord + ", " +
                 "tokenToRecord = " + mTokenToRecord + ", " +
-                "waitingToRecord = " + mWaitingToRecord +
+                "waitingForConsent = " + mWaitingForConsent +
         " }";
     }
 
@@ -346,7 +346,7 @@
                 && mContentToRecord == that.mContentToRecord
                 && mDisplayToRecord == that.mDisplayToRecord
                 && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
-                && mWaitingToRecord == that.mWaitingToRecord;
+                && mWaitingForConsent == that.mWaitingForConsent;
     }
 
     @Override
@@ -360,7 +360,7 @@
         _hash = 31 * _hash + mContentToRecord;
         _hash = 31 * _hash + mDisplayToRecord;
         _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
-        _hash = 31 * _hash + Boolean.hashCode(mWaitingToRecord);
+        _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent);
         return _hash;
     }
 
@@ -371,7 +371,7 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         byte flg = 0;
-        if (mWaitingToRecord) flg |= 0x10;
+        if (mWaitingForConsent) flg |= 0x10;
         if (mTokenToRecord != null) flg |= 0x8;
         dest.writeByte(flg);
         dest.writeInt(mVirtualDisplayId);
@@ -392,7 +392,7 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         byte flg = in.readByte();
-        boolean waitingToRecord = (flg & 0x10) != 0;
+        boolean waitingForConsent = (flg & 0x10) != 0;
         int virtualDisplayId = in.readInt();
         int contentToRecord = in.readInt();
         int displayToRecord = in.readInt();
@@ -411,7 +411,7 @@
 
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
-        this.mWaitingToRecord = waitingToRecord;
+        this.mWaitingForConsent = waitingForConsent;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -441,7 +441,7 @@
         private @RecordContent int mContentToRecord;
         private int mDisplayToRecord;
         private @Nullable IBinder mTokenToRecord;
-        private boolean mWaitingToRecord;
+        private boolean mWaitingForConsent;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -506,10 +506,10 @@
          * <p>Only set on the server side to sanitize any input from the client process.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setWaitingToRecord(boolean value) {
+        public @NonNull Builder setWaitingForConsent(boolean value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x10;
-            mWaitingToRecord = value;
+            mWaitingForConsent = value;
             return this;
         }
 
@@ -531,14 +531,14 @@
                 mTokenToRecord = null;
             }
             if ((mBuilderFieldsSet & 0x10) == 0) {
-                mWaitingToRecord = false;
+                mWaitingForConsent = false;
             }
             ContentRecordingSession o = new ContentRecordingSession(
                     mVirtualDisplayId,
                     mContentToRecord,
                     mDisplayToRecord,
                     mTokenToRecord,
-                    mWaitingToRecord);
+                    mWaitingForConsent);
             return o;
         }
 
@@ -551,10 +551,10 @@
     }
 
     @DataClass.Generated(
-            time = 1679855157534L,
+            time = 1683628463074L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
-            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingToRecord\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 8cff066..efd50e7 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -46,8 +46,6 @@
 import android.os.RemoteException;
 import android.text.Selection;
 import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -740,7 +738,10 @@
         // Since the same CharSequence instance may be reused in the TextView, we need to make
         // a copy of its content so that its value will not be changed by subsequent updates
         // in the TextView.
-        final CharSequence eventText = stringOrSpannedStringWithoutNoCopySpans(text);
+        CharSequence trimmed = TextUtils.trimToParcelableSize(text);
+        final CharSequence eventText = trimmed != null && trimmed == text
+                ? trimmed.toString()
+                : trimmed;
 
         final int composingStart;
         final int composingEnd;
@@ -761,16 +762,6 @@
                         .setSelectionIndex(startIndex, endIndex)));
     }
 
-    private CharSequence stringOrSpannedStringWithoutNoCopySpans(CharSequence source) {
-        if (source == null) {
-            return null;
-        } else if (source instanceof Spanned) {
-            return new SpannableString(source, /* ignoreNoCopySpan= */ true);
-        } else {
-            return source.toString();
-        }
-    }
-
     /** Public because is also used by ViewRootImpl */
     public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
         mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 1762a58..044a31f 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -1052,14 +1052,21 @@
         }
 
         void writeToParcel(Parcel out, boolean simple) {
-            TextUtils.writeToParcel(mText, out, 0);
+            CharSequence text = TextUtils.trimToParcelableSize(mText);
+            TextUtils.writeToParcel(text, out, 0);
             out.writeFloat(mTextSize);
             out.writeInt(mTextStyle);
             out.writeInt(mTextColor);
             if (!simple) {
+                int selectionStart = text != null
+                        ? Math.min(mTextSelectionStart, text.length())
+                        : mTextSelectionStart;
+                int selectionEnd = text != null
+                        ? Math.min(mTextSelectionEnd, text.length())
+                        : mTextSelectionEnd;
                 out.writeInt(mTextBackgroundColor);
-                out.writeInt(mTextSelectionStart);
-                out.writeInt(mTextSelectionEnd);
+                out.writeInt(selectionStart);
+                out.writeInt(selectionEnd);
                 out.writeIntArray(mLineCharOffsets);
                 out.writeIntArray(mLineBaselines);
                 out.writeString(mHint);
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/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 21bdf09..e5d5676 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -652,9 +652,7 @@
     char saveResolvedClassesDelayMsOptsBuf[
             sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
     char profileMinSavePeriodOptsBuf[sizeof("-Xps-min-save-period-ms:")-1 + PROPERTY_VALUE_MAX];
-    char profileMinFirstSaveOptsBuf[
-            sizeof("-Xps-min-first-save-ms:")-1 + PROPERTY_VALUE_MAX];
-    char madviseRandomOptsBuf[sizeof("-XX:MadviseRandomAccess:")-1 + PROPERTY_VALUE_MAX];
+    char profileMinFirstSaveOptsBuf[sizeof("-Xps-min-first-save-ms:") - 1 + PROPERTY_VALUE_MAX];
     char madviseWillNeedFileSizeVdex[
             sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
     char madviseWillNeedFileSizeOdex[
@@ -866,13 +864,8 @@
                        jitprithreadweightOptBuf,
                        "-Xjitprithreadweight:");
 
-    parseRuntimeOption("dalvik.vm.jittransitionweight",
-                       jittransitionweightOptBuf,
+    parseRuntimeOption("dalvik.vm.jittransitionweight", jittransitionweightOptBuf,
                        "-Xjittransitionweight:");
-    /*
-     * Madvise related options.
-     */
-    parseRuntimeOption("dalvik.vm.madvise-random", madviseRandomOptsBuf, "-XX:MadviseRandomAccess:");
 
     /*
      * Use default platform configuration as limits for madvising,
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 2a0e476..4b4e722 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1675,6 +1675,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/am\/ActivityManagerService.java"
     },
+    "-584061725": {
+      "message": "Content Recording: Accept session updating same display %d with granted consent, with an existing session %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+    },
     "-583031528": {
       "message": "%s",
       "level": "INFO",
@@ -2167,6 +2173,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "-125383273": {
+      "message": "Content Recording: waiting to record, so do nothing",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-124316973": {
       "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
deleted file mode 100644
index d8f3561..0000000
--- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
-    <path
-        android:fillColor="@color/tv_pip_menu_focus_border"
-        android:pathData="M7,10l5,5 5,-5H7z"/>
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
deleted file mode 100644
index 3e0011c..0000000
--- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
-    <path
-        android:fillColor="@color/tv_pip_menu_focus_border"
-        android:pathData="M14,7l-5,5 5,5V7z"/>
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
deleted file mode 100644
index f6b3c72..0000000
--- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
-    <path
-        android:fillColor="@color/tv_pip_menu_focus_border"
-        android:pathData="M10,17l5,-5 -5,-5v10z"/>
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
deleted file mode 100644
index 1a34462..0000000
--- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
-    <path
-        android:fillColor="@color/tv_pip_menu_focus_border"
-        android:pathData="M7,14l5,-5 5,5H7z"/>
-</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index ab64f9e..82a358c 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -104,9 +104,7 @@
         android:layout_centerHorizontal="true"
         android:layout_alignParentTop="true"
         android:alpha="0"
-        android:contentDescription="@string/a11y_action_pip_move_up"
-        android:elevation="@dimen/pip_menu_arrow_elevation"
-        android:src="@drawable/pip_ic_move_up" />
+        android:contentDescription="@string/a11y_action_pip_move_up"/>
 
     <ImageView
         android:id="@+id/tv_pip_menu_arrow_right"
@@ -115,9 +113,7 @@
         android:layout_centerVertical="true"
         android:layout_alignParentRight="true"
         android:alpha="0"
-        android:contentDescription="@string/a11y_action_pip_move_right"
-        android:elevation="@dimen/pip_menu_arrow_elevation"
-        android:src="@drawable/pip_ic_move_right" />
+        android:contentDescription="@string/a11y_action_pip_move_right"/>
 
     <ImageView
         android:id="@+id/tv_pip_menu_arrow_down"
@@ -126,9 +122,7 @@
         android:layout_centerHorizontal="true"
         android:layout_alignParentBottom="true"
         android:alpha="0"
-        android:contentDescription="@string/a11y_action_pip_move_down"
-        android:elevation="@dimen/pip_menu_arrow_elevation"
-        android:src="@drawable/pip_ic_move_down" />
+        android:contentDescription="@string/a11y_action_pip_move_down"/>
 
     <ImageView
         android:id="@+id/tv_pip_menu_arrow_left"
@@ -137,7 +131,5 @@
         android:layout_centerVertical="true"
         android:layout_alignParentLeft="true"
         android:alpha="0"
-        android:contentDescription="@string/a11y_action_pip_move_left"
-        android:elevation="@dimen/pip_menu_arrow_elevation"
-        android:src="@drawable/pip_ic_move_left" />
+        android:contentDescription="@string/a11y_action_pip_move_left"/>
 </RelativeLayout>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index adbf656..fd82563 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -33,8 +33,8 @@
     <!-- outer space minus border width -->
     <dimen name="pip_menu_outer_space_frame">20dp</dimen>
 
-    <dimen name="pip_menu_arrow_size">24dp</dimen>
-    <dimen name="pip_menu_arrow_elevation">5dp</dimen>
+    <dimen name="pip_menu_arrow_size">12dp</dimen>
+    <dimen name="pip_menu_arrow_elevation">1dp</dimen>
 
     <dimen name="pip_menu_elevation_no_menu">1dp</dimen>
     <dimen name="pip_menu_elevation_move_menu">7dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index e6933ca..5f7fb12 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -27,6 +27,10 @@
     <color name="tv_pip_menu_focus_border">#E8EAED</color>
     <color name="tv_pip_menu_dim_layer">#990E0E0F</color>
     <color name="tv_pip_menu_background">#1E232C</color>
+    <!-- Normally, the arrow color would be the same as the focus border color. But due to
+        optical illusion that looks too dark on the screen. That's why we define a separate
+        (lighter) arrow color. -->
+    <color name="tv_pip_menu_arrow_color">#F1F3F4</color>
 
     <color name="tv_pip_edu_text">#99D2E3FC</color>
     <color name="tv_pip_edu_text_home_icon">#D2E3FC</color>
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/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index d076418..613791c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -31,12 +31,19 @@
 import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
 import android.os.Handler;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -91,6 +98,8 @@
     private final ImageView mArrowLeft;
     private final TvWindowMenuActionButton mA11yDoneButton;
 
+    private final int mArrowElevation;
+
     private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
     private final Rect mCurrentPipBounds = new Rect();
     private int mCurrentPipGravity;
@@ -131,21 +140,70 @@
         mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
         mA11yDoneButton = findViewById(R.id.tv_pip_menu_done_button);
 
-        mResizeAnimationDuration = context.getResources().getInteger(
-                R.integer.config_pipResizeAnimationDuration);
-        mPipMenuFadeAnimationDuration = context.getResources()
-                .getInteger(R.integer.tv_window_menu_fade_animation_duration);
+        final Resources res = context.getResources();
+        mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
+        mPipMenuFadeAnimationDuration =
+                res.getInteger(R.integer.tv_window_menu_fade_animation_duration);
+        mPipMenuOuterSpace = res.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+        mPipMenuBorderWidth = res.getDimensionPixelSize(R.dimen.pip_menu_border_width);
+        mArrowElevation = res.getDimensionPixelSize(R.dimen.pip_menu_arrow_elevation);
 
-        mPipMenuOuterSpace = context.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
-        mPipMenuBorderWidth = context.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+        initMoveArrows();
 
         mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, this);
         mEduTextContainer = (ViewGroup) findViewById(R.id.tv_pip_menu_edu_text_container);
         mEduTextContainer.addView(mEduTextDrawer);
     }
 
+    private void initMoveArrows() {
+        final int arrowSize =
+                mContext.getResources().getDimensionPixelSize(R.dimen.pip_menu_arrow_size);
+        final Path arrowPath = createArrowPath(arrowSize);
+
+        final ShapeDrawable arrowDrawable = new ShapeDrawable();
+        arrowDrawable.setShape(new PathShape(arrowPath, arrowSize, arrowSize));
+        arrowDrawable.setTint(mContext.getResources().getColor(R.color.tv_pip_menu_arrow_color));
+
+        final ViewOutlineProvider arrowOutlineProvider = new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setPath(createArrowPath(view.getMeasuredHeight()));
+            }
+        };
+
+        initArrow(mArrowRight, arrowOutlineProvider, arrowDrawable, 0);
+        initArrow(mArrowDown, arrowOutlineProvider, arrowDrawable, 90);
+        initArrow(mArrowLeft, arrowOutlineProvider, arrowDrawable, 180);
+        initArrow(mArrowUp, arrowOutlineProvider, arrowDrawable, 270);
+    }
+
+    /**
+     * Creates a Path for a movement arrow in the MODE_MOVE_MENU. The resulting Path is a simple
+     * right-pointing triangle with its tip in the center of a size x size square:
+     *  _ _ _ _ _
+     * |*        |
+     * |* *      |
+     * |*   *    |
+     * |* *      |
+     * |* _ _ _ _|
+     *
+     */
+    private Path createArrowPath(int size) {
+        final Path triangle = new Path();
+        triangle.lineTo(0, size);
+        triangle.lineTo(size / 2, size / 2);
+        triangle.close();
+        return triangle;
+    }
+
+    private void initArrow(View v, ViewOutlineProvider arrowOutlineProvider, Drawable arrowDrawable,
+            int rotation) {
+        v.setOutlineProvider(arrowOutlineProvider);
+        v.setBackground(arrowDrawable);
+        v.setRotation(rotation);
+        v.setElevation(mArrowElevation);
+    }
+
     void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
         if (targetBounds == null) {
             return;
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/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/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 964e4b2..00ccea1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManager.EnforcingUser;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -118,12 +119,13 @@
             return enforcedAdmin;
         }
 
-        final int restrictionSource = enforcingUsers.get(0).getUserRestrictionSource();
+        final EnforcingUser enforcingUser = enforcingUsers.get(0);
+        final int restrictionSource = enforcingUser.getUserRestrictionSource();
         if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
             return null;
         }
 
-        final EnforcedAdmin admin = getProfileOrDeviceOwner(context, userHandle);
+        final EnforcedAdmin admin = getProfileOrDeviceOwner(context, enforcingUser.getUserHandle());
         if (admin != null) {
             return admin;
         }
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/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_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/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/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/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 5d403e1..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
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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e837786..0fdd7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2112,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 {
@@ -2914,6 +2917,10 @@
                 && mBarState == StatusBarState.SHADE;
     }
 
+    private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
+        return mUnlockedScreenOffAnimationController.isAnimationPlaying();
+    }
+
     @Override
     public boolean shouldHideStatusBarIconsWhenExpanded() {
         if (mIsLaunchAnimationRunning) {
@@ -3498,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;
@@ -3973,6 +3980,7 @@
                 || isPanelVisibleBecauseOfHeadsUp()
                 || mTracking
                 || mHeightAnimator != null
+                || isPanelVisibleBecauseScrimIsAnimatingOff()
                 && !mIsSpringBackAnimation;
     }
 
@@ -4939,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/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 31b361f..81fe3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -218,7 +218,7 @@
             containerPadding = 0
             stackScrollMargin = bottomStableInsets + notificationsBottomMargin
         }
-        val qsContainerPadding = if (!(isQSCustomizing || isQSDetailShowing)) {
+        val qsContainerPadding = if (!isQSDetailShowing) {
             // We also want this padding in the bottom in these cases
             if (splitShadeEnabled) {
                 stackScrollMargin - scrimShadeBottomMargin - footerActionsOffset
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/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/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/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/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/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/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index c5495e1..a5a9de5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1106,7 +1106,7 @@
     }
 
     @Test
-    public void shadeExpanded_inShadeState() {
+    public void shadeFullyExpanded_inShadeState() {
         mStatusBarStateController.setState(SHADE);
 
         mNotificationPanelViewController.setExpandedHeight(0);
@@ -1118,7 +1118,7 @@
     }
 
     @Test
-    public void shadeExpanded_onKeyguard() {
+    public void shadeFullyExpanded_onKeyguard() {
         mStatusBarStateController.setState(KEYGUARD);
 
         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
@@ -1127,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/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index dfb1bce..168cbb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -229,25 +229,6 @@
     }
 
     @Test
-    fun testCustomizingInSinglePaneShade() {
-        disableSplitShade()
-        controller.setCustomizerShowing(true)
-
-        // always sets spacings to 0
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-    }
-
-    @Test
     fun testDetailShowingInSinglePaneShade() {
         disableSplitShade()
         controller.setDetailShowing(true)
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/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/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/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d9b3157..d6e692c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -62,6 +62,7 @@
 import android.os.BundleMerger;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PowerExemptionManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -878,12 +879,20 @@
             mLocalHandler.sendMessageDelayed(
                     Message.obtain(mLocalHandler, MSG_BG_ACTIVITY_START_TIMEOUT, args), timeout);
         }
-
         if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) {
-            mService.tempAllowlistUidLocked(queue.uid,
-                    r.options.getTemporaryAppAllowlistDuration(),
-                    r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(),
-                    r.options.getTemporaryAppAllowlistType(), r.callingUid);
+            if (r.options.getTemporaryAppAllowlistType()
+                    == PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
+                // Only delay freezer, don't add to any temp allowlist
+                // TODO: Add a unit test
+                mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
+                        CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
+                        r.options.getTemporaryAppAllowlistDuration());
+            } else {
+                mService.tempAllowlistUidLocked(queue.uid,
+                        r.options.getTemporaryAppAllowlistDuration(),
+                        r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(),
+                        r.options.getTemporaryAppAllowlistType(), r.callingUid);
+            }
         }
 
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 3e82d55..7773190 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -47,6 +47,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 
 import android.annotation.IntDef;
+import android.annotation.UptimeMillisLong;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
@@ -1278,14 +1279,35 @@
         return true;
     }
 
+    /**
+     * Returns the earliest time (relative) from now that the app can be frozen.
+     * @param app The app to update
+     * @param delayMillis How much to delay freezing by
+     */
+    @GuardedBy("mProcLock")
+    private long updateEarliestFreezableTime(ProcessRecord app, long delayMillis) {
+        final long now = SystemClock.uptimeMillis();
+        app.mOptRecord.setEarliestFreezableTime(
+                Math.max(app.mOptRecord.getEarliestFreezableTime(), now + delayMillis));
+        return app.mOptRecord.getEarliestFreezableTime() - now;
+    }
+
     // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
     @GuardedBy("mAm")
     void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason) {
+        unfreezeTemporarily(app, reason, mFreezerDebounceTimeout);
+    }
+
+    // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
+    @GuardedBy("mAm")
+    void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis) {
         if (mUseFreezer) {
             synchronized (mProcLock) {
+                // Move the earliest freezable time further, if necessary
+                final long delay = updateEarliestFreezableTime(app, delayMillis);
                 if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
                     unfreezeAppLSP(app, reason);
-                    freezeAppAsyncLSP(app);
+                    freezeAppAsyncLSP(app, delay);
                 }
             }
         }
@@ -1293,11 +1315,17 @@
 
     @GuardedBy({"mAm", "mProcLock"})
     void freezeAppAsyncLSP(ProcessRecord app) {
-        freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false);
+        freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, mFreezerDebounceTimeout));
     }
 
     @GuardedBy({"mAm", "mProcLock"})
-    void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) {
+    private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
+        freezeAppAsyncInternalLSP(app, delayMillis, false);
+    }
+
+    @GuardedBy({"mAm", "mProcLock"})
+    void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
+            boolean force) {
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         if (opt.isPendingFreeze()) {
             // Skip redundant DO_FREEZE message
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index e8c8f6d..7841b69 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.annotation.UptimeMillisLong;
 import android.app.ActivityManagerInternal.OomAdjReason;
 
 import com.android.internal.annotations.GuardedBy;
@@ -119,6 +120,12 @@
     @GuardedBy("mProcLock")
     private boolean mPendingFreeze;
 
+    /**
+     * This is the soonest the process can be allowed to freeze, in uptime millis
+     */
+    @GuardedBy("mProcLock")
+    private @UptimeMillisLong long mEarliestFreezableTimeMillis;
+
     @GuardedBy("mProcLock")
     long getLastCompactTime() {
         return mLastCompactTime;
@@ -264,6 +271,16 @@
     }
 
     @GuardedBy("mProcLock")
+    @UptimeMillisLong long getEarliestFreezableTime() {
+        return mEarliestFreezableTimeMillis;
+    }
+
+    @GuardedBy("mProcLock")
+    void setEarliestFreezableTime(@UptimeMillisLong long earliestFreezableTimeMillis) {
+        mEarliestFreezableTimeMillis = earliestFreezableTimeMillis;
+    }
+
+    @GuardedBy("mProcLock")
     boolean isFreezeExempt() {
         return mFreezeExempt;
     }
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/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index dd422c3..355981a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11053,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/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5771a04..34ad91c4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1560,7 +1560,7 @@
                 // VirtualDisplay has been successfully constructed.
                 session.setVirtualDisplayId(displayId);
                 // Don't start mirroring until user re-grants consent.
-                session.setWaitingToRecord(waitForPermissionConsent);
+                session.setWaitingForConsent(waitForPermissionConsent);
 
                 // We set the content recording session here on the server side instead of using
                 // a second AIDL call in MediaProjection. By ensuring that a virtual display has
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/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 377b8cf..38631c8 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -406,7 +406,7 @@
                 return;
             }
             if (mProjectionGrant.mSession == null
-                    || !mProjectionGrant.mSession.isWaitingToRecord()) {
+                    || !mProjectionGrant.mSession.isWaitingForConsent()) {
                 Slog.w(TAG, "Reusing token: Ignore consent result " + consentResult
                         + " if not waiting for the result.");
                 return;
@@ -445,7 +445,7 @@
      */
     private void setReviewedConsentSessionLocked(@Nullable ContentRecordingSession session) {
         if (session != null) {
-            session.setWaitingToRecord(false);
+            session.setWaitingForConsent(false);
             session.setVirtualDisplayId(mProjectionGrant.mVirtualDisplayId);
         }
 
@@ -490,7 +490,7 @@
             // Supposedly the package has re-used the user's consent; confirm the provided details
             // against the current projection token before re-using the current projection.
             if (mProjectionGrant == null || mProjectionGrant.mSession == null
-                    || !mProjectionGrant.mSession.isWaitingToRecord()) {
+                    || !mProjectionGrant.mSession.isWaitingForConsent()) {
                 Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
                         + "instance");
                 return null;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4e043aa..7e88e13 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2854,9 +2854,8 @@
                     UserHandle.USER_NULL, UserManager.RESTRICTION_SOURCE_SYSTEM));
         }
 
-        synchronized (mRestrictionsLock) {
-            result.addAll(mDevicePolicyUserRestrictions.getEnforcingUsers(restrictionKey, userId));
-        }
+        result.addAll(getDevicePolicyManagerInternal()
+                .getUserRestrictionSources(restrictionKey, userId));
         return result;
     }
 
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 2cbe8db..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -37,7 +37,6 @@
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -69,8 +68,6 @@
     private static final String ACTIVITY_RECOGNITION_TAGS =
             "android:activity_recognition_allow_listed_tags";
     private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
-    private static final boolean SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED =
-            SystemProperties.getBoolean("ro.hotword.detection_service_required", false);
 
     @NonNull
     private final Object mLock = new Object();
@@ -204,11 +201,8 @@
 
     private static boolean isHotwordDetectionServiceRequired(PackageManager pm) {
         // The HotwordDetectionService APIs aren't ready yet for Auto or TV.
-        if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
-                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
-            return false;
-        }
-        return SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED;
+        return !(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
     }
 
     @Override
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/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/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index b808a55d..6a7e764 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -276,6 +276,12 @@
             return;
         }
 
+        if (mContentRecordingSession.isWaitingForConsent()) {
+            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
+                    + "nothing");
+            return;
+        }
+
         mRecordedWindowContainer = retrieveRecordedWindowContainer();
         if (mRecordedWindowContainer == null) {
             // Either the token is missing, or the window associated with the token is missing.
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index 4da55e2..f24ba5a 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -56,8 +56,8 @@
      * Updates the current recording session.
      * <p>Handles the following scenarios:
      * <ul>
-     *         <li>Invalid scenarios: The incoming session is malformed, or the incoming session is
-     *         identical to the current session</li>
+     *         <li>Invalid scenarios: The incoming session is malformed.</li>
+     *         <li>Ignored scenario: the incoming session is identical to the current session.</li>
      *         <li>Start Scenario: Starting a new session. Recording begins immediately.</li>
      *         <li>Takeover Scenario: Occurs during a Start Scenario, if a pre-existing session was
      *         in-progress. For example, recording on VirtualDisplay "app_foo" was ongoing. A
@@ -66,6 +66,8 @@
      *         begin.</li>
      *         <li>Stopping scenario: The incoming session is null and there is currently an ongoing
      *         session. The controller stops recording.</li>
+     *         <li>Updating scenario: There is an update for the same display, where recording
+     *         was previously not taking place but is now permitted to go ahead.</li>
      * </ul>
      *
      * @param incomingSession The incoming recording session (either an update to a current session
@@ -78,20 +80,28 @@
         if (incomingSession != null && !ContentRecordingSession.isValid(incomingSession)) {
             return;
         }
-        // Invalid scenario: ignore identical incoming session.
+        final boolean hasSessionUpdatedWithConsent =
+                mSession != null && incomingSession != null && mSession.isWaitingForConsent()
+                        && !incomingSession.isWaitingForConsent();
         if (ContentRecordingSession.isProjectionOnSameDisplay(mSession, incomingSession)) {
-            // TODO(242833866): if incoming session is no longer waiting to record, allow
-            //  the update through.
-
-            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                    "Content Recording: Ignoring session on same display %d, with an existing "
-                            + "session %s",
-                    incomingSession.getVirtualDisplayId(), mSession.getVirtualDisplayId());
-            return;
+            if (hasSessionUpdatedWithConsent) {
+                // Updating scenario: accept an incoming session updating the current display.
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Accept session updating same display %d with granted "
+                                + "consent, with an existing session %s",
+                        incomingSession.getVirtualDisplayId(), mSession.getVirtualDisplayId());
+            } else {
+                // Ignored scenario: ignore identical incoming session.
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Ignoring session on same display %d, with an existing "
+                                + "session %s",
+                        incomingSession.getVirtualDisplayId(), mSession.getVirtualDisplayId());
+                return;
+            }
         }
         DisplayContent incomingDisplayContent = null;
-        // Start scenario: recording begins immediately.
         if (incomingSession != null) {
+            // Start scenario: recording begins immediately.
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                     "Content Recording: Handle incoming session on display %d, with a "
                             + "pre-existing session %s", incomingSession.getVirtualDisplayId(),
@@ -106,11 +116,14 @@
                 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.
+            // Updating scenario: Explicitly ask ContentRecorder to update, since no config or
+            // display change will trigger an update from the DisplayContent.
+            if (hasSessionUpdatedWithConsent) {
+                incomingDisplayContent.updateRecording();
+            }
         }
         // Takeover and stopping scenario: stop recording on the pre-existing session.
-        if (mSession != null) {
+        if (mSession != null && !hasSessionUpdatedWithConsent) {
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                     "Content Recording: Pause the recording session on display %s",
                     mDisplayContent.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 251a087..fb6d036 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6693,9 +6693,10 @@
 
     /**
      * Start recording if this DisplayContent no longer has content. Stop recording if it now
-     * has content or the display is not on.
+     * has content or the display is not on. Update recording if the content has changed (for
+     * example, the user has granted consent to token re-use, so we can now start mirroring).
      */
-    @VisibleForTesting void updateRecording() {
+    void updateRecording() {
         if (mContentRecorder == null || !mContentRecorder.isContentRecordingSessionSet()) {
             if (!setDisplayMirroring()) {
                 return;
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index df968f9..cf7c718 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -605,6 +605,22 @@
     }
 
     /**
+     * Retrieves the values set for the provided {@code policyDefinition} by each admin.
+     */
+    @NonNull
+    <V> LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getGlobalPoliciesSetByAdmins(
+            @NonNull PolicyDefinition<V> policyDefinition) {
+        Objects.requireNonNull(policyDefinition);
+
+        synchronized (mLock) {
+            if (!hasGlobalPolicyLocked(policyDefinition)) {
+                return new LinkedHashMap<>();
+            }
+            return getGlobalPolicyStateLocked(policyDefinition).getPoliciesSetByAdmins();
+        }
+    }
+
+    /**
      * Returns the policies set by the given admin that share the same
      * {@link PolicyKey#getIdentifier()} as the provided {@code policyDefinition}.
      *
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f875e34..2846b39 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -410,6 +410,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManager.EnforcingUser;
 import android.os.UserManager.UserRestrictionSource;
 import android.os.storage.StorageManager;
 import android.permission.AdminPermissionControlParams;
@@ -16346,6 +16347,39 @@
                 return List.of(bundle);
             });
         }
+
+        public List<EnforcingUser> getUserRestrictionSources(String restriction,
+                @UserIdInt int userId) {
+            PolicyDefinition<Boolean> policy =
+                    PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction);
+
+            Set<EnforcingAdmin> localAdmins =
+                    mDevicePolicyEngine.getLocalPoliciesSetByAdmins(policy, userId).keySet();
+
+            Set<EnforcingAdmin> globalAdmins =
+                    mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policy).keySet();
+
+            List<EnforcingUser> enforcingUsers = new ArrayList();
+            enforcingUsers.addAll(getEnforcingUsers(localAdmins));
+            enforcingUsers.addAll(getEnforcingUsers(globalAdmins));
+            return enforcingUsers;
+        }
+
+        private List<EnforcingUser> getEnforcingUsers(Set<EnforcingAdmin> admins) {
+            List<EnforcingUser> enforcingUsers = new ArrayList();
+            ComponentName deviceOwner = mOwners.getDeviceOwnerComponent();
+            for (EnforcingAdmin admin : admins) {
+                if (deviceOwner != null
+                        && deviceOwner.getPackageName().equals(admin.getPackageName())) {
+                    enforcingUsers.add(new EnforcingUser(admin.getUserId(),
+                            UserManager.RESTRICTION_SOURCE_DEVICE_OWNER));
+                } else {
+                    enforcingUsers.add(new EnforcingUser(admin.getUserId(),
+                            UserManager.RESTRICTION_SOURCE_PROFILE_OWNER));
+                }
+            }
+            return enforcingUsers;
+        }
     }
 
     private Intent createShowAdminSupportIntent(int userId) {
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/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 9cd22dd..b19a13c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -402,13 +402,15 @@
         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
                 JobParameters.STOP_REASON_DEVICE_STATE,
                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
-        assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, rescheduledJob.getEarliestRunTime());
         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
 
         // failure = 0, systemStop = 2
         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
                 JobParameters.STOP_REASON_DEVICE_STATE,
                 JobParameters.INTERNAL_STOP_REASON_PREEMPT);
+        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
         // failure = 0, systemStop = 3
         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
                 JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 8981160..021f2d1 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -323,6 +323,61 @@
     }
 
     @Test
+    public void testMultipleBrightnessEvents() {
+        final float brightnessOne = 0.2f;
+        final float brightnessTwo = 0.4f;
+        final float brightnessThree = 0.6f;
+        final float brightnessFour = 0.3f;
+        final String displayId = "1234";
+        final float[] luxValues = new float[]{1.0f};
+
+        startTracker(mTracker);
+        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long sensorTime2 = sensorTime + TimeUnit.SECONDS.toMillis(20);
+        final long sensorTime3 = sensorTime2 + TimeUnit.SECONDS.toMillis(30);
+        final long sensorTime4 = sensorTime3 + TimeUnit.SECONDS.toMillis(40);
+        final long originalTime = mInjector.currentTimeMillis();
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        notifyBrightnessChanged(mTracker, brightnessOne, displayId, luxValues,
+                new long[] {sensorTime});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(20));
+        notifyBrightnessChanged(mTracker, brightnessTwo, displayId, luxValues,
+                new long[] {sensorTime2});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(30));
+        notifyBrightnessChanged(mTracker, brightnessThree, displayId, luxValues,
+                new long[] {sensorTime3});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(40));
+        notifyBrightnessChanged(mTracker, brightnessFour, displayId, luxValues,
+                new long[] {sensorTime4});
+        mTracker.stop();
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        assertEquals(4, events.size());
+        BrightnessChangeEvent eventOne = events.get(0);
+        assertEquals(brightnessOne, eventOne.brightness, FLOAT_DELTA);
+        assertEquals(originalTime,
+                eventOne.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventTwo = events.get(1);
+        assertEquals(brightnessTwo, eventTwo.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(20),
+                eventTwo.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventThree = events.get(2);
+        assertEquals(brightnessThree, eventThree.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(50),
+                eventThree.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventFour = events.get(3);
+        assertEquals(brightnessFour, eventFour.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(90),
+                eventFour.luxTimestamps[0]);
+    }
+
+    @Test
     public void testBrightnessFullPopulatedEvent() {
         final int initialBrightness = 230;
         final int brightness = 130;
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index acfc073..5438750 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1079,7 +1079,7 @@
         verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
                 mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
         ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.isWaitingToRecord()).isTrue();
+        assertThat(session.isWaitingForConsent()).isTrue();
     }
 
     @Test
@@ -1114,7 +1114,7 @@
         assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
         assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
         assertThat(session.getDisplayToRecord()).isEqualTo(displayToRecord);
-        assertThat(session.isWaitingToRecord()).isFalse();
+        assertThat(session.isWaitingForConsent()).isFalse();
     }
 
     @Test
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/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 275533f..b91a6cb 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -139,7 +139,7 @@
         doReturn(mPackageManager).when(mContext).getPackageManager();
 
         mClock = new OffsettableClock.Stopped();
-        mWaitingDisplaySession.setWaitingToRecord(true);
+        mWaitingDisplaySession.setWaitingForConsent(true);
         mWaitingDisplaySession.setVirtualDisplayId(5);
 
         mAppInfo.targetSdkVersion = 32;
@@ -484,7 +484,7 @@
                 mSessionCaptor.capture());
         // Examine latest value.
         final ContentRecordingSession capturedSession = mSessionCaptor.getValue();
-        assertThat(capturedSession.isWaitingToRecord()).isFalse();
+        assertThat(capturedSession.isWaitingForConsent()).isFalse();
         assertThat(capturedSession.getVirtualDisplayId()).isEqualTo(INVALID_DISPLAY);
     }
 
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/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index ad9f710..bbec091 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -79,6 +79,8 @@
     private Task mTask;
     private final ContentRecordingSession mDisplaySession =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
+    private final ContentRecordingSession mWaitingDisplaySession =
+            ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
     private ContentRecordingSession mTaskSession;
     private static Point sSurfaceSize;
     private ContentRecorder mContentRecorder;
@@ -120,6 +122,10 @@
         mTaskSession = ContentRecordingSession.createTaskSession(sTaskWindowContainerToken);
         mTaskSession.setVirtualDisplayId(displayId);
 
+        // GIVEN a session is waiting for the user to review consent.
+        mWaitingDisplaySession.setVirtualDisplayId(displayId);
+        mWaitingDisplaySession.setWaitingForConsent(true);
+
         mConfigListener = new ConfigListener();
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 mContext.getMainExecutor(), mConfigListener);
@@ -221,6 +227,18 @@
     }
 
     @Test
+    public void testUpdateRecording_waitingForConsent() {
+        mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
     public void testOnConfigurationChanged_neverRecording() {
         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 6cda038..52226c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -24,10 +24,8 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 
-import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.view.ContentRecordingSession;
 
@@ -36,6 +34,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests for the {@link ContentRecordingController} class.
@@ -49,12 +49,20 @@
 public class ContentRecordingControllerTests extends WindowTestsBase {
     private final ContentRecordingSession mDefaultSession =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
+    private final ContentRecordingSession mWaitingDisplaySession =
+            ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
 
     private int mVirtualDisplayId;
     private DisplayContent mVirtualDisplayContent;
+    private WindowContainer.RemoteToken mRootTaskToken;
+
+    @Mock
+    private WindowContainer mTaskWindowContainer;
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
+
         // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
         mVirtualDisplayContent = new TestDisplayContent.Builder(mAtm, 500, 600).build();
         mVirtualDisplayId = mVirtualDisplayContent.getDisplayId();
@@ -62,6 +70,11 @@
         spyOn(mVirtualDisplayContent);
 
         mDefaultSession.setVirtualDisplayId(mVirtualDisplayId);
+        mWaitingDisplaySession.setVirtualDisplayId(mVirtualDisplayId);
+        mWaitingDisplaySession.setWaitingForConsent(true);
+
+        mRootTaskToken = new WindowContainer.RemoteToken(mTaskWindowContainer);
+        mTaskWindowContainer.mRemoteToken = mRootTaskToken;
     }
 
     @Test
@@ -92,7 +105,7 @@
     }
 
     @Test
-    public void testSetContentRecordingSessionLocked_newDisplaySession_accepted() {
+    public void testSetContentRecordingSessionLocked_newSession_accepted() {
         ContentRecordingController controller = new ContentRecordingController();
         // GIVEN a valid display session.
         // WHEN updating the session.
@@ -104,15 +117,37 @@
     }
 
     @Test
-    public void testSetContentRecordingSessionLocked_updateCurrentDisplaySession_notAccepted() {
+    public void testSetContentRecordingSessionLocked_updateSession_noLongerWaiting_accepted() {
+        ContentRecordingController controller = new ContentRecordingController();
+        // GIVEN a valid display session already in place.
+        controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
+        verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
+                mWaitingDisplaySession);
+
+        // WHEN updating the session on the same display, so no longer waiting to record.
+        ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
+                mRootTaskToken.toWindowContainerToken().asBinder());
+        sessionUpdate.setVirtualDisplayId(mVirtualDisplayId);
+        sessionUpdate.setWaitingForConsent(false);
+        controller.setContentRecordingSessionLocked(sessionUpdate, mWm);
+
+        ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
+        // THEN the session was accepted.
+        assertThat(resultingSession).isEqualTo(sessionUpdate);
+        verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
+        verify(mVirtualDisplayContent).updateRecording();
+    }
+
+    @Test
+    public void testSetContentRecordingSessionLocked_invalidUpdateSession_notWaiting_notAccepted() {
         ContentRecordingController controller = new ContentRecordingController();
         // GIVEN a valid display session already in place.
         controller.setContentRecordingSessionLocked(mDefaultSession, mWm);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(mDefaultSession);
 
         // WHEN updating the session on the same display.
-        ContentRecordingSession sessionUpdate =
-                ContentRecordingSession.createTaskSession(mock(IBinder.class));
+        ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
+                mRootTaskToken.toWindowContainerToken().asBinder());
         sessionUpdate.setVirtualDisplayId(mVirtualDisplayId);
         controller.setContentRecordingSessionLocked(sessionUpdate, mWm);
 
@@ -123,7 +158,7 @@
     }
 
     @Test
-    public void testSetContentRecordingSessionLocked_disableCurrentDisplaySession_accepted() {
+    public void testSetContentRecordingSessionLocked_disableCurrentSession_accepted() {
         ContentRecordingController controller = new ContentRecordingController();
         // GIVEN a valid display session already in place.
         controller.setContentRecordingSessionLocked(mDefaultSession, mWm);
@@ -141,7 +176,7 @@
     }
 
     @Test
-    public void testSetContentRecordingSessionLocked_takeOverCurrentDisplaySession_accepted() {
+    public void testSetContentRecordingSessionLocked_takeOverCurrentSession_accepted() {
         ContentRecordingController controller = new ContentRecordingController();
         // GIVEN a valid display session already in place.
         controller.setContentRecordingSessionLocked(mDefaultSession, mWm);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 72f2c1d..cf236dd 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2540,10 +2540,15 @@
 
         @Override
         public void reportChooserSelection(@NonNull String packageName, int userId,
-                String contentType, String[] annotations, String action) {
+                @NonNull String contentType, String[] annotations, @NonNull String action) {
             if (packageName == null) {
                 throw new IllegalArgumentException("Package selection must not be null.");
             }
+            // A valid contentType and action must be provided for chooser selection events.
+            if (contentType == null || contentType.isBlank()
+                    || action == null || action.isBlank()) {
+                return;
+            }
             // Verify if this package exists before reporting an event for it.
             if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
                 Slog.w(TAG, "Event report user selecting an invalid package");
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;