Merge "Don't update the standby bucket for apps that were just restored." into tm-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2587462..a6f47d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -109,6 +109,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseLongArray;
+import android.util.SparseSetArray;
 import android.util.TimeUtils;
 import android.view.Display;
 import android.widget.Toast;
@@ -452,6 +453,12 @@
     private final Map<String, String> mAppStandbyProperties = new ArrayMap<>();
 
     /**
+     * Set of apps that were restored via backup & restore, per user, that need their
+     * standby buckets to be adjusted when installed.
+     */
+    private final SparseSetArray<String> mAppsToRestoreToRare = new SparseSetArray<>();
+
+    /**
      * List of app-ids of system packages, populated on boot, when system services are ready.
      */
     private final ArrayList<Integer> mSystemPackagesAppIds = new ArrayList<>();
@@ -1614,18 +1621,29 @@
         final int reason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED;
         final long nowElapsed = mInjector.elapsedRealtime();
         for (String packageName : restoredApps) {
-            // If the package is not installed, don't allow the bucket to be set.
+            // If the package is not installed, don't allow the bucket to be set. Instead, add it
+            // to a list of all packages whose buckets need to be adjusted when installed.
             if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
-                Slog.e(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+                Slog.i(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+                mAppsToRestoreToRare.add(userId, packageName);
                 continue;
             }
 
-            final int standbyBucket = getAppStandbyBucket(packageName, userId, nowElapsed, false);
-            // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
-            if (standbyBucket == STANDBY_BUCKET_NEVER) {
-                setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RARE, reason,
-                        nowElapsed, false);
-            }
+            restoreAppToRare(packageName, userId, nowElapsed, reason);
+        }
+        // Clear out the list of restored apps that need to have their standby buckets adjusted
+        // if they still haven't been installed eight hours after restore.
+        // Note: if the device reboots within these first 8 hours, this list will be lost since it's
+        // not persisted - this is the expected behavior for now and may be updated in the future.
+        mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), 8 * ONE_HOUR);
+    }
+
+    /** Adjust the standby bucket of the given package for the user to RARE. */
+    private void restoreAppToRare(String pkgName, int userId, long nowElapsed, int reason) {
+        final int standbyBucket = getAppStandbyBucket(pkgName, userId, nowElapsed, false);
+        // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
+        if (standbyBucket == STANDBY_BUCKET_NEVER) {
+            setAppStandbyBucket(pkgName, userId, STANDBY_BUCKET_RARE, reason, nowElapsed, false);
         }
     }
 
@@ -2120,15 +2138,24 @@
                 }
                 // component-level enable/disable can affect bucketing, so we always
                 // reevaluate that for any PACKAGE_CHANGED
-                mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkgName)
-                    .sendToTarget();
+                if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                    mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkgName)
+                            .sendToTarget();
+                }
             }
             if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
                     Intent.ACTION_PACKAGE_ADDED.equals(action))) {
                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     maybeUnrestrictBuggyApp(pkgName, userId);
-                } else {
+                } else if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                     clearAppIdleForPackage(pkgName, userId);
+                } else {
+                    // Package was just added and it's not being replaced.
+                    if (mAppsToRestoreToRare.contains(userId, pkgName)) {
+                        restoreAppToRare(pkgName, userId, mInjector.elapsedRealtime(),
+                                REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED);
+                        mAppsToRestoreToRare.remove(userId, pkgName);
+                    }
                 }
             }
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d6fa02c6..94a6382 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9514,6 +9514,16 @@
                 "lock_screen_show_silent_notifications";
 
         /**
+         * Indicates whether "seen" notifications should be suppressed from the lockscreen.
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        public static final String LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS =
+                "lock_screen_show_only_unseen_notifications";
+
+        /**
          * Indicates whether snooze options should be shown on notifications
          * <p>
          * Type: int (0 for false, 1 for true)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
index 7624b69..fe60037 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -27,9 +27,10 @@
  */
 public class AcceptOnceConsumer<T> implements Consumer<T> {
     private final Consumer<T> mCallback;
-    private final DataProducer<T> mProducer;
+    private final AcceptOnceProducerCallback<T> mProducer;
 
-    public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) {
+    public AcceptOnceConsumer(@NonNull AcceptOnceProducerCallback<T> producer,
+            @NonNull Consumer<T> callback) {
         mProducer = producer;
         mCallback = callback;
     }
@@ -37,6 +38,20 @@
     @Override
     public void accept(@NonNull T t) {
         mCallback.accept(t);
-        mProducer.removeDataChangedCallback(this);
+        mProducer.onConsumerReadyToBeRemoved(this);
+    }
+
+    /**
+     * Interface to allow the {@link AcceptOnceConsumer} to notify the client that created it,
+     * when it is ready to be removed. This allows the client to remove the consumer object
+     * when it deems it is safe to do so.
+     * @param <T> The type of data this callback accepts through {@link #onConsumerReadyToBeRemoved}
+     */
+    public interface AcceptOnceProducerCallback<T> {
+
+        /**
+         * Notifies that the given {@code callback} is ready to be removed
+         */
+        void onConsumerReadyToBeRemoved(Consumer<T> callback);
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index cbaa277..46c925a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -19,6 +19,7 @@
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Optional;
 import java.util.Set;
@@ -31,11 +32,14 @@
  *
  * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
  */
-public abstract class BaseDataProducer<T> implements DataProducer<T> {
+public abstract class BaseDataProducer<T> implements DataProducer<T>,
+        AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
+    @GuardedBy("mLock")
+    private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
 
     /**
      * Adds a callback to the set of callbacks listening for data. Data is delivered through
@@ -85,6 +89,26 @@
             for (Consumer<T> callback : mCallbacks) {
                 callback.accept(value);
             }
+            removeFinishedCallbacksLocked();
+        }
+    }
+
+    /**
+     * Removes any callbacks that notified us through {@link #onConsumerReadyToBeRemoved(Consumer)}
+     * that they are ready to be removed.
+     */
+    @GuardedBy("mLock")
+    private void removeFinishedCallbacksLocked() {
+        for (Consumer<T> callback: mCallbacksToRemove) {
+            mCallbacks.remove(callback);
+        }
+        mCallbacksToRemove.clear();
+    }
+
+    @Override
+    public void onConsumerReadyToBeRemoved(Consumer<T> callback) {
+        synchronized (mLock) {
+            mCallbacksToRemove.add(callback);
         }
     }
 }
\ No newline at end of file
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 9f52bf1..308bb3e 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -100,6 +100,8 @@
     private final Object mListenerLock = new Object();
     private OnImageReleasedListener mListener;
     private ListenerHandler mListenerHandler;
+    private final Object mCloseLock = new Object();
+    private boolean mIsWriterValid = false;
     private long mNativeContext;
 
     private int mWidth;
@@ -305,6 +307,8 @@
             ImageUtils.getEstimatedNativeAllocBytes(mWidth, mHeight,
                 useLegacyImageFormat ? imageFormat : hardwareBufferFormat, /*buffer count*/ 1);
         VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
+
+        mIsWriterValid = true;
     }
 
     private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
@@ -448,14 +452,17 @@
      * @see Image#close
      */
     public Image dequeueInputImage() {
-        if (mDequeuedImages.size() >= mMaxImages) {
-            throw new IllegalStateException("Already dequeued max number of Images " + mMaxImages);
+        synchronized (mCloseLock) {
+            if (mDequeuedImages.size() >= mMaxImages) {
+                throw new IllegalStateException(
+                        "Already dequeued max number of Images " + mMaxImages);
+            }
+            WriterSurfaceImage newImage = new WriterSurfaceImage(this);
+            nativeDequeueInputImage(mNativeContext, newImage);
+            mDequeuedImages.add(newImage);
+            newImage.mIsImageValid = true;
+            return newImage;
         }
-        WriterSurfaceImage newImage = new WriterSurfaceImage(this);
-        nativeDequeueInputImage(mNativeContext, newImage);
-        mDequeuedImages.add(newImage);
-        newImage.mIsImageValid = true;
-        return newImage;
     }
 
     /**
@@ -513,49 +520,53 @@
         if (image == null) {
             throw new IllegalArgumentException("image shouldn't be null");
         }
-        boolean ownedByMe = isImageOwnedByMe(image);
-        if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
-            throw new IllegalStateException("Image from ImageWriter is invalid");
-        }
 
-        // For images from other components that have non-null owner, need to detach first,
-        // then attach. Images without owners must already be attachable.
-        if (!ownedByMe) {
-            if ((image.getOwner() instanceof ImageReader)) {
-                ImageReader prevOwner = (ImageReader) image.getOwner();
-
-                prevOwner.detachImage(image);
-            } else if (image.getOwner() != null) {
-                throw new IllegalArgumentException("Only images from ImageReader can be queued to"
-                        + " ImageWriter, other image source is not supported yet!");
+        synchronized (mCloseLock) {
+            boolean ownedByMe = isImageOwnedByMe(image);
+            if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
+                throw new IllegalStateException("Image from ImageWriter is invalid");
             }
 
-            attachAndQueueInputImage(image);
-            // This clears the native reference held by the original owner.
-            // When this Image is detached later by this ImageWriter, the
-            // native memory won't be leaked.
-            image.close();
-            return;
-        }
+            // For images from other components that have non-null owner, need to detach first,
+            // then attach. Images without owners must already be attachable.
+            if (!ownedByMe) {
+                if ((image.getOwner() instanceof ImageReader)) {
+                    ImageReader prevOwner = (ImageReader) image.getOwner();
 
-        Rect crop = image.getCropRect();
-        nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
-                crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
-                image.getScalingMode());
+                    prevOwner.detachImage(image);
+                } else if (image.getOwner() != null) {
+                    throw new IllegalArgumentException(
+                            "Only images from ImageReader can be queued to"
+                                    + " ImageWriter, other image source is not supported yet!");
+                }
 
-        /**
-         * Only remove and cleanup the Images that are owned by this
-         * ImageWriter. Images detached from other owners are only temporarily
-         * owned by this ImageWriter and will be detached immediately after they
-         * are released by downstream consumers, so there is no need to keep
-         * track of them in mDequeuedImages.
-         */
-        if (ownedByMe) {
-            mDequeuedImages.remove(image);
-            // Do not call close here, as close is essentially cancel image.
-            WriterSurfaceImage wi = (WriterSurfaceImage) image;
-            wi.clearSurfacePlanes();
-            wi.mIsImageValid = false;
+                attachAndQueueInputImage(image);
+                // This clears the native reference held by the original owner.
+                // When this Image is detached later by this ImageWriter, the
+                // native memory won't be leaked.
+                image.close();
+                return;
+            }
+
+            Rect crop = image.getCropRect();
+            nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
+                    crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
+                    image.getScalingMode());
+
+            /**
+             * Only remove and cleanup the Images that are owned by this
+             * ImageWriter. Images detached from other owners are only temporarily
+             * owned by this ImageWriter and will be detached immediately after they
+             * are released by downstream consumers, so there is no need to keep
+             * track of them in mDequeuedImages.
+             */
+            if (ownedByMe) {
+                mDequeuedImages.remove(image);
+                // Do not call close here, as close is essentially cancel image.
+                WriterSurfaceImage wi = (WriterSurfaceImage) image;
+                wi.clearSurfacePlanes();
+                wi.mIsImageValid = false;
+            }
         }
     }
 
@@ -691,17 +702,23 @@
      */
     @Override
     public void close() {
-        setOnImageReleasedListener(null, null);
-        for (Image image : mDequeuedImages) {
-            image.close();
-        }
-        mDequeuedImages.clear();
-        nativeClose(mNativeContext);
-        mNativeContext = 0;
+        synchronized (mCloseLock) {
+            if (!mIsWriterValid) {
+                return;
+            }
+            setOnImageReleasedListener(null, null);
+            for (Image image : mDequeuedImages) {
+                image.close();
+            }
+            mDequeuedImages.clear();
+            nativeClose(mNativeContext);
+            mNativeContext = 0;
 
-        if (mEstimatedNativeAllocBytes > 0) {
-            VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
-            mEstimatedNativeAllocBytes = 0;
+            if (mEstimatedNativeAllocBytes > 0) {
+                VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
+                mEstimatedNativeAllocBytes = 0;
+            }
+            mIsWriterValid = false;
         }
     }
 
@@ -790,10 +807,16 @@
         @Override
         public void handleMessage(Message msg) {
             OnImageReleasedListener listener;
-            synchronized (mListenerLock) {
+            boolean isWriterValid;
+            synchronized (ImageWriter.this.mListenerLock) {
                 listener = mListener;
             }
-            if (listener != null) {
+            // Check to make sure we don't accidentally queue images after the writer is
+            // closed or closing
+            synchronized (ImageWriter.this.mCloseLock) {
+                isWriterValid = ImageWriter.this.mIsWriterValid;
+            }
+            if (listener != null && isWriterValid) {
                 listener.onImageReleased(ImageWriter.this);
             }
         }
@@ -813,10 +836,14 @@
         }
 
         final Handler handler;
+        final boolean isWriterValid;
         synchronized (iw.mListenerLock) {
             handler = iw.mListenerHandler;
         }
-        if (handler != null) {
+        synchronized (iw.mCloseLock) {
+            isWriterValid = iw.mIsWriterValid;
+        }
+        if (handler != null && isWriterValid) {
             handler.sendEmptyMessage(0);
         }
     }
@@ -1050,6 +1077,9 @@
         private int mTransform = 0; //Default no transform
         private int mScalingMode = 0; //Default frozen scaling mode
 
+        private final Object mCloseLock = new Object(); // lock to protect against multiple
+                                                        // simultaneous calls to close()
+
         public WriterSurfaceImage(ImageWriter writer) {
             mOwner = writer;
             mWidth = writer.mWidth;
@@ -1192,8 +1222,10 @@
 
         @Override
         public void close() {
-            if (mIsImageValid) {
-                getOwner().abortImage(this);
+            synchronized (mCloseLock) {
+                if (mIsImageValid) {
+                    getOwner().abortImage(this);
+                }
             }
         }
 
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 46a94fd..99b15db 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -165,6 +165,9 @@
     <!-- Default for Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS -->
     <bool name="def_lock_screen_allow_private_notifications">true</bool>
 
+    <!-- Default for Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS -->
+    <bool name="def_lock_screen_show_only_unseen_notifications">false</bool>
+
     <!-- Default for Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 1==on -->
     <integer name="def_heads_up_enabled">1</integer>
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 92a938c..1b0b6b4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -146,6 +146,7 @@
         Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
         Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
         Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+        Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
         Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
         Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
         Settings.Secure.ZEN_DURATION,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index eabf4cc..4fa490f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -219,6 +219,7 @@
         VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ded7e785..84a5593 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3631,7 +3631,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 211;
+            private static final int SETTINGS_VERSION = 212;
 
             private final int mUserId;
 
@@ -5527,6 +5527,26 @@
                     }
                     currentVersion = 211;
                 }
+                if (currentVersion == 211) {
+                    // Version 211: Set default value for
+                    // Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    final Setting lockScreenUnseenSetting = secureSettings
+                            .getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS);
+                    if (lockScreenUnseenSetting.isNull()) {
+                        final boolean defSetting = getContext().getResources()
+                                .getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications);
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
+                                Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                                defSetting ? "1" : "0",
+                                null /* tag */,
+                                true /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 212;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6d88e51..2d756ae 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -779,6 +779,10 @@
     <!-- Duration in milliseconds of the y-translation animation when entering a dream -->
     <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
 
+    <!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
+    active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
+    <integer name="config_defaultDockUserTimeoutMs">60000</integer>
+
     <!-- Icons that don't show in a collapsed non-keyguard statusbar -->
     <string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
         <item>@*android:string/status_bar_volume</item>
@@ -820,6 +824,8 @@
     slot. If the user did make a choice, even if the choice is the "None" option, the default is
     ignored. -->
     <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+        <item>bottom_start:home</item>
+        <item>bottom_end:wallet</item>
     </string-array>
 
 </resources>
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index 64c2ef1..7de0a5e 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -88,20 +88,18 @@
     The chain is set to "spread" so that the progress bar can be weighted to fill any empty space.
      -->
     <Constraint
-        android:id="@+id/media_scrubbing_elapsed_time"
+        android:id="@+id/actionPrev"
         android:layout_width="48dp"
         android:layout_height="48dp"
         app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toLeftOf="@id/actionPrev"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintHorizontal_chainStyle="spread" />
 
     <Constraint
-        android:id="@+id/actionPrev"
+        android:id="@+id/media_scrubbing_elapsed_time"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/media_scrubbing_elapsed_time"
-        app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
+        app:layout_constraintLeft_toRightOf="@id/actionPrev"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintHorizontal_chainStyle="spread" />
 
@@ -109,7 +107,7 @@
         android:id="@+id/media_progress_bar"
         android:layout_width="0dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/actionPrev"
+        app:layout_constraintLeft_toRightOf="@id/media_scrubbing_elapsed_time"
         app:layout_constraintRight_toLeftOf="@id/actionNext"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintHorizontal_weight="1" />
@@ -118,7 +116,6 @@
         android:id="@+id/actionNext"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
         app:layout_constraintRight_toLeftOf="@id/media_scrubbing_total_time"
         app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -126,7 +123,6 @@
         android:id="@+id/media_scrubbing_total_time"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/actionNext"
         app:layout_constraintRight_toLeftOf="@id/action0"
         app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -134,7 +130,6 @@
         android:id="@+id/action0"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/media_scrubbing_total_time"
         app:layout_constraintRight_toLeftOf="@id/action1"
         app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -142,7 +137,6 @@
         android:id="@+id/action1"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/action0"
         app:layout_constraintRight_toLeftOf="@id/action2"
         app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -150,7 +144,6 @@
         android:id="@+id/action2"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/action1"
         app:layout_constraintRight_toLeftOf="@id/action3"
         app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -158,7 +151,6 @@
         android:id="@+id/action3"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/action2"
         app:layout_constraintRight_toLeftOf="@id/action4"
         app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -166,7 +158,6 @@
         android:id="@+id/action4"
         android:layout_width="48dp"
         android:layout_height="48dp"
-        app:layout_constraintLeft_toRightOf="@id/action3"
         app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintBottom_toBottomOf="parent" />
 </ConstraintSet>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 4133802..76252d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -16,8 +16,13 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import android.database.ContentObserver
+import android.os.UserHandle
+import android.provider.Settings
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
@@ -30,11 +35,20 @@
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxy
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 /**
@@ -45,16 +59,19 @@
 class KeyguardCoordinator
 @Inject
 constructor(
+    @Background private val bgDispatcher: CoroutineDispatcher,
     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
     private val keyguardRepository: KeyguardRepository,
     private val notifPipelineFlags: NotifPipelineFlags,
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+    private val secureSettings: SecureSettings,
     private val seenNotifsProvider: SeenNotificationsProviderImpl,
     private val statusBarStateController: StatusBarStateController,
 ) : Coordinator {
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
+    private var unseenFilterEnabled = false
 
     override fun attach(pipeline: NotifPipeline) {
         setupInvalidateNotifListCallbacks()
@@ -71,6 +88,7 @@
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
         scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+        scope.launch { invalidateWhenUnseenSettingChanges() }
     }
 
     private suspend fun clearUnseenWhenKeyguardIsDismissed() {
@@ -85,6 +103,36 @@
         }
     }
 
+    private suspend fun invalidateWhenUnseenSettingChanges() {
+        secureSettings
+            // emit whenever the setting has changed
+            .settingChangesForUser(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                UserHandle.USER_ALL,
+            )
+            // perform a query immediately
+            .onStart { emit(Unit) }
+            // for each change, lookup the new value
+            .map {
+                secureSettings.getBoolForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    UserHandle.USER_CURRENT,
+                )
+            }
+            // perform lookups on the bg thread pool
+            .flowOn(bgDispatcher)
+            // only track the most recent emission, if events are happening faster than they can be
+            // consumed
+            .conflate()
+            // update local field and invalidate if necessary
+            .collect { setting ->
+                if (setting != unseenFilterEnabled) {
+                    unseenFilterEnabled = setting
+                    unseenNotifFilter.invalidateList("unseen setting changed")
+                }
+            }
+    }
+
     private val collectionListener =
         object : NotifCollectionListener {
             override fun onEntryAdded(entry: NotificationEntry) {
@@ -112,6 +160,8 @@
 
             override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
                 when {
+                    // Don't apply filter if the setting is disabled
+                    !unseenFilterEnabled -> false
                     // Don't apply filter if the keyguard isn't currently showing
                     !keyguardRepository.isKeyguardShowing() -> false
                     // Don't apply the filter if the notification is unseen
@@ -165,3 +215,15 @@
         private val SEEN_TIMEOUT = 5.seconds
     }
 }
+
+private fun SettingsProxy.settingChangesForUser(name: String, userHandle: Int): Flow<Unit> =
+    conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+        registerContentObserverForUser(name, observer, userHandle)
+        awaitClose { unregisterContentObserver(observer) }
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 67091a9..3d65713 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -165,6 +165,10 @@
 
     @Test
     fun `remembers selections by user`() = runTest {
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf<String>(),
+        )
         val slot1 = "slot_1"
         val slot2 = "slot_2"
         val affordance1 = "affordance_1"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index c40488a..c187a3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -114,6 +114,11 @@
                 userHandle = UserHandle.SYSTEM,
             )
 
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf<String>(),
+        )
+
         underTest =
             KeyguardQuickAffordanceRepository(
                 appContext = context,
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 d97571bc..58a8530 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
@@ -20,6 +20,7 @@
 import android.os.UserHandle
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -290,6 +291,11 @@
     @Test
     fun select() =
         testScope.runTest {
+            overrideResource(
+                R.array.config_keyguardQuickAffordanceDefaults,
+                arrayOf<String>(),
+            )
+
             featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 5f19fac..be6b1dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -18,6 +18,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.Notification
+import android.os.UserHandle
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -29,6 +31,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider
@@ -38,6 +41,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +51,8 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
 import java.util.function.Consumer
@@ -176,6 +182,42 @@
     }
 
     @Test
+    fun unseenFilterInvalidatesWhenSettingChanges() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing
+        keyguardRepository.setKeyguardShowing(false)
+        runKeyguardCoordinatorTest {
+            // GIVEN: A notification is present
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // GIVEN: The setting for filtering unseen notifications is disabled
+            showOnlyUnseenNotifsOnKeyguardSetting = false
+
+            // GIVEN: The pipeline has registered the unseen filter for invalidation
+            val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
+            unseenFilter.setInvalidationListener(invalidationListener)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is not filtered out
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+            // WHEN: The secure setting is changed
+            showOnlyUnseenNotifsOnKeyguardSetting = true
+
+            // THEN: The pipeline is invalidated
+            verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), anyString())
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+    }
+
+    @Test
     fun unseenFilterAllowsNewNotif() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
@@ -276,22 +318,32 @@
     private fun runKeyguardCoordinatorTest(
         testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
     ) {
-        val testScope = TestScope(UnconfinedTestDispatcher())
+        val testDispatcher = UnconfinedTestDispatcher()
+        val testScope = TestScope(testDispatcher)
+        val fakeSettings = FakeSettings().apply {
+            putBool(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, true)
+        }
         val seenNotificationsProvider = SeenNotificationsProviderImpl()
         val keyguardCoordinator =
             KeyguardCoordinator(
+                testDispatcher,
                 keyguardNotifVisibilityProvider,
                 keyguardRepository,
                 notifPipelineFlags,
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,
+                fakeSettings,
                 seenNotificationsProvider,
                 statusBarStateController,
             )
         keyguardCoordinator.attach(notifPipeline)
         testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
-            KeyguardCoordinatorTestScope(keyguardCoordinator, testScope, seenNotificationsProvider)
-                .testBlock()
+            KeyguardCoordinatorTestScope(
+                keyguardCoordinator,
+                testScope,
+                seenNotificationsProvider,
+                fakeSettings,
+            ).testBlock()
         }
     }
 
@@ -299,6 +351,7 @@
         private val keyguardCoordinator: KeyguardCoordinator,
         private val scope: TestScope,
         val seenNotificationsProvider: SeenNotificationsProvider,
+        private val fakeSettings: FakeSettings,
     ) : CoroutineScope by scope {
         val testScheduler: TestCoroutineScheduler
             get() = scope.testScheduler
@@ -316,5 +369,19 @@
         val collectionListener: NotifCollectionListener by lazy {
             withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
         }
+
+        var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
+            get() =
+                fakeSettings.getBoolForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    UserHandle.USER_CURRENT,
+                )
+            set(value) {
+                fakeSettings.putBoolForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    value,
+                    UserHandle.USER_CURRENT,
+                )
+            }
     }
 }