Merge "Add @Platinum and @Ignore to DemotingTestWithoutBugDetector linter." into tm-qpr-dev
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 16b18c85..3f2fa21 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -31,7 +31,6 @@
 import android.os.Process;
 import android.permission.PermissionManager;
 import android.util.ArraySet;
-import android.util.Log;
 
 import com.android.internal.annotations.Immutable;
 
@@ -88,8 +87,6 @@
  */
 @Immutable
 public final class AttributionSource implements Parcelable {
-    private static final String TAG = "AttributionSource";
-
     private static final String DESCRIPTOR = "android.content.AttributionSource";
 
     private static final Binder sDefaultToken = new Binder(DESCRIPTOR);
@@ -157,20 +154,9 @@
     AttributionSource(@NonNull Parcel in) {
         this(AttributionSourceState.CREATOR.createFromParcel(in));
 
-        if (!Binder.isDirectlyHandlingTransaction()) {
-            Log.e(TAG, "Unable to verify calling UID #" + mAttributionSourceState.uid + " PID #"
-                    + mAttributionSourceState.pid + " when not handling Binder transaction; "
-                    + "clearing.");
-            mAttributionSourceState.pid = -1;
-            mAttributionSourceState.uid = -1;
-            mAttributionSourceState.packageName = null;
-            mAttributionSourceState.attributionTag = null;
-            mAttributionSourceState.next = null;
-        } else {
-            // Since we just unpacked this object as part of it transiting a Binder
-            // call, this is the perfect time to enforce that its UID and PID can be trusted
-            enforceCallingUidAndPid();
-        }
+        // Since we just unpacked this object as part of it transiting a Binder
+        // call, this is the perfect time to enforce that its UID and PID can be trusted
+        enforceCallingUidAndPid();
     }
 
     /** @hide */
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index caa118a..e1c9b3c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -517,6 +517,12 @@
         <permission name="android.permission.BIND_WALLPAPER"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.wallpaper">
+        <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+        <permission name="android.permission.BIND_WALLPAPER"/>
+        <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.dynsystem">
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a581aa..482158e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -91,6 +91,22 @@
         val sampledRegion = calculateSampledRegion(sampledView)
         val regions = ArrayList<RectF>()
         val sampledRegionWithOffset = convertBounds(sampledRegion)
+
+        if (
+            sampledRegionWithOffset.left < 0.0 ||
+                sampledRegionWithOffset.right > 1.0 ||
+                sampledRegionWithOffset.top < 0.0 ||
+                sampledRegionWithOffset.bottom > 1.0
+        ) {
+            android.util.Log.e(
+                "RegionSampler",
+                "view out of bounds: $sampledRegion | " +
+                    "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
+                Exception()
+            )
+            return
+        }
+
         regions.add(sampledRegionWithOffset)
 
         wallpaperManager?.removeOnColorsChangedListener(this)
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 8323d09..f005bab 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,8 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
 
 @Module(includes = [
     FeatureFlagsDebugStartableModule::class,
@@ -35,7 +37,8 @@
     abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
 
     @Binds
-    abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+    @IntoSet
+    abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
 
     @Module
     companion object {
@@ -44,5 +47,10 @@
         fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
             return FlagManager(context, handler)
         }
+
+        @JvmStatic
+        @Provides
+        @Named(ConditionalRestarter.RESTART_DELAY)
+        fun provideRestartDelaySec(): Long = 1
     }
 }
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 87beff7..927d4604b 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,9 @@
 
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
 
 @Module(includes = [
     FeatureFlagsReleaseStartableModule::class,
@@ -29,5 +32,18 @@
     abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
 
     @Binds
-    abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
+    @IntoSet
+    abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+
+    @Binds
+    @IntoSet
+    abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+
+    @Module
+    companion object {
+        @JvmStatic
+        @Provides
+        @Named(ConditionalRestarter.RESTART_DELAY)
+        fun provideRestartDelaySec(): Long = 30
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 92ee373..4aaa566 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.content.res.Resources
+import android.graphics.Rect
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
@@ -119,10 +120,6 @@
 
     private val mLayoutChangedListener =
         object : View.OnLayoutChangeListener {
-            private var currentSmallClockView: View? = null
-            private var currentLargeClockView: View? = null
-            private var currentSmallClockLocation = IntArray(2)
-            private var currentLargeClockLocation = IntArray(2)
 
             override fun onLayoutChange(
                 view: View?,
@@ -135,6 +132,8 @@
                 oldRight: Int,
                 oldBottom: Int
             ) {
+                view?.removeOnLayoutChangeListener(this)
+
                 val parent = (view?.parent) as FrameLayout
 
                 // don't pass in negative bounds when clocks are in transition state
@@ -142,31 +141,12 @@
                     return
                 }
 
-                // SMALL CLOCK
-                if (parent.id == R.id.lockscreen_clock_view) {
-                    // view bounds have changed due to clock size changing (i.e. different character
-                    // widths)
-                    // AND/OR the view has been translated when transitioning between small and
-                    // large clock
-                    if (
-                        view != currentSmallClockView ||
-                            !view.locationOnScreen.contentEquals(currentSmallClockLocation)
-                    ) {
-                        currentSmallClockView = view
-                        currentSmallClockLocation = view.locationOnScreen
-                        updateRegionSampler(view)
-                    }
-                }
-                // LARGE CLOCK
-                else if (parent.id == R.id.lockscreen_clock_view_large) {
-                    if (
-                        view != currentLargeClockView ||
-                            !view.locationOnScreen.contentEquals(currentLargeClockLocation)
-                    ) {
-                        currentLargeClockView = view
-                        currentLargeClockLocation = view.locationOnScreen
-                        updateRegionSampler(view)
-                    }
+                val currentViewRect = Rect(left, top, right, bottom)
+                val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
+
+                if (currentViewRect.width() != oldViewRect.width() ||
+                    currentViewRect.height() != oldViewRect.height()) {
+                    updateRegionSampler(view)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
new file mode 100644
index 0000000..b20e33a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Restarts the process after all passed in [Condition]s are true. */
+class ConditionalRestarter
+@Inject
+constructor(
+    private val systemExitRestarter: SystemExitRestarter,
+    private val conditions: Set<@JvmSuppressWildcards Condition>,
+    @Named(RESTART_DELAY) private val restartDelaySec: Long,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : Restarter {
+
+    private var restartJob: Job? = null
+    private var pendingReason = ""
+    private var androidRestartRequested = false
+
+    override fun restartSystemUI(reason: String) {
+        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
+        scheduleRestart(reason)
+    }
+
+    override fun restartAndroid(reason: String) {
+        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.")
+        androidRestartRequested = true
+        scheduleRestart(reason)
+    }
+
+    private fun scheduleRestart(reason: String = "") {
+        pendingReason = if (reason.isEmpty()) pendingReason else reason
+
+        if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
+            if (restartJob == null) {
+                restartJob =
+                    applicationScope.launch(backgroundDispatcher) {
+                        delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
+                        restartNow()
+                    }
+            }
+        } else {
+            restartJob?.cancel()
+            restartJob = null
+        }
+    }
+
+    private fun restartNow() {
+        if (androidRestartRequested) {
+            systemExitRestarter.restartAndroid(pendingReason)
+        } else {
+            systemExitRestarter.restartSystemUI(pendingReason)
+        }
+    }
+
+    interface Condition {
+        /**
+         * Should return true if the system is ready to restart.
+         *
+         * A call to this function means that we want to restart and are waiting for this condition
+         * to return true.
+         *
+         * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_
+         * ready to restart. At that point, this method will be called again to verify that the
+         * system is ready.
+         *
+         * Multiple calls to an instance of this method may happen for a single restart attempt if
+         * multiple [Condition]s are being checked. If any one [Condition] returns false, all the
+         * [Condition]s will need to be rechecked on the next restart attempt.
+         */
+        fun canRestartNow(retryFn: () -> Unit): Boolean
+    }
+
+    companion object {
+        const val RESTART_DELAY = "restarter_restart_delay"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
deleted file mode 100644
index a6956a4..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import javax.inject.Inject
-
-/** Restarts SystemUI when the screen is locked. */
-class FeatureFlagsDebugRestarter
-@Inject
-constructor(
-    private val wakefulnessLifecycle: WakefulnessLifecycle,
-    private val systemExitRestarter: SystemExitRestarter,
-) : Restarter {
-
-    private var androidRestartRequested = false
-    private var pendingReason = ""
-
-    val observer =
-        object : WakefulnessLifecycle.Observer {
-            override fun onFinishedGoingToSleep() {
-                Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
-                restartNow()
-            }
-        }
-
-    override fun restartSystemUI(reason: String) {
-        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
-        Log.i(FeatureFlagsDebug.TAG, reason)
-        scheduleRestart(reason)
-    }
-
-    override fun restartAndroid(reason: String) {
-        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
-        androidRestartRequested = true
-        scheduleRestart(reason)
-    }
-
-    fun scheduleRestart(reason: String) {
-        pendingReason = reason
-        if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
-            restartNow()
-        } else {
-            wakefulnessLifecycle.addObserver(observer)
-        }
-    }
-
-    private fun restartNow() {
-        if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid(pendingReason)
-        } else {
-            systemExitRestarter.restartSystemUI(pendingReason)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
deleted file mode 100644
index c08266c..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-/** Restarts SystemUI when the device appears idle. */
-class FeatureFlagsReleaseRestarter
-@Inject
-constructor(
-    private val wakefulnessLifecycle: WakefulnessLifecycle,
-    private val batteryController: BatteryController,
-    @Background private val bgExecutor: DelayableExecutor,
-    private val systemExitRestarter: SystemExitRestarter
-) : Restarter {
-    var listenersAdded = false
-    var pendingRestart: Runnable? = null
-    private var pendingReason = ""
-    var androidRestartRequested = false
-
-    val observer =
-        object : WakefulnessLifecycle.Observer {
-            override fun onFinishedGoingToSleep() {
-                scheduleRestart(pendingReason)
-            }
-        }
-
-    val batteryCallback =
-        object : BatteryController.BatteryStateChangeCallback {
-            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-                scheduleRestart(pendingReason)
-            }
-        }
-
-    override fun restartSystemUI(reason: String) {
-        Log.d(
-            FeatureFlagsDebug.TAG,
-            "SystemUI Restart requested. Restarting when plugged in and idle."
-        )
-        scheduleRestart(reason)
-    }
-
-    override fun restartAndroid(reason: String) {
-        Log.d(
-            FeatureFlagsDebug.TAG,
-            "Android Restart requested. Restarting when plugged in and idle."
-        )
-        androidRestartRequested = true
-        scheduleRestart(reason)
-    }
-
-    private fun scheduleRestart(reason: String) {
-        // Don't bother adding listeners twice.
-        pendingReason = reason
-        if (!listenersAdded) {
-            listenersAdded = true
-            wakefulnessLifecycle.addObserver(observer)
-            batteryController.addCallback(batteryCallback)
-        }
-        if (
-            wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
-        ) {
-            if (pendingRestart == null) {
-                pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
-            }
-        } else if (pendingRestart != null) {
-            pendingRestart?.run()
-            pendingRestart = null
-        }
-    }
-
-    private fun restartNow() {
-        Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
-        if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid(pendingReason)
-        } else {
-            systemExitRestarter.restartSystemUI(pendingReason)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 0054d26..3c50125 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.flags
 
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import javax.inject.Named
@@ -22,6 +23,8 @@
 /** Module containing shared code for all FeatureFlag implementations. */
 @Module
 interface FlagsCommonModule {
+    @Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter
+
     companion object {
         const val ALL_FLAGS = "all_flags"
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
new file mode 100644
index 0000000..3120638
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Returns true when the device is plugged in. */
+class PluggedInCondition
+@Inject
+constructor(
+    private val batteryController: BatteryController,
+) : ConditionalRestarter.Condition {
+
+    var listenersAdded = false
+    var retryFn: (() -> Unit)? = null
+
+    val batteryCallback =
+        object : BatteryController.BatteryStateChangeCallback {
+            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+                retryFn?.invoke()
+            }
+        }
+
+    override fun canRestartNow(retryFn: () -> Unit): Boolean {
+        if (!listenersAdded) {
+            listenersAdded = true
+            batteryController.addCallback(batteryCallback)
+        }
+
+        this.retryFn = retryFn
+
+        return batteryController.isPluggedIn
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
new file mode 100644
index 0000000..49e61af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class ScreenIdleCondition
+@Inject
+constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+) : ConditionalRestarter.Condition {
+
+    var listenersAdded = false
+    var retryFn: (() -> Unit)? = null
+
+    val observer =
+        object : WakefulnessLifecycle.Observer {
+            override fun onFinishedGoingToSleep() {
+                retryFn?.invoke()
+            }
+        }
+
+    override fun canRestartNow(retryFn: () -> Unit): Boolean {
+        if (!listenersAdded) {
+            listenersAdded = true
+            wakefulnessLifecycle.addObserver(observer)
+        }
+
+        this.retryFn = retryFn
+
+        return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6023bc2..525b2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1343,13 +1343,9 @@
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key) ?: return
-        val isEligibleForResume =
-            removed.isLocalSession() ||
-                (mediaFlags.isRemoteResumeAllowed() &&
-                    removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
         if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
-        } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) {
+        } else if (isAbleToResume(removed)) {
             convertToResumePlayer(key, removed)
         } else if (mediaFlags.isRetainingPlayersEnabled()) {
             handlePossibleRemoval(key, removed, notificationRemoved = true)
@@ -1369,6 +1365,14 @@
         handlePossibleRemoval(key, updated)
     }
 
+    private fun isAbleToResume(data: MediaData): Boolean {
+        val isEligibleForResume =
+            data.isLocalSession() ||
+                (mediaFlags.isRemoteResumeAllowed() &&
+                    data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
+        return useMediaResumption && data.resumeAction != null && isEligibleForResume
+    }
+
     /**
      * Convert to resume state if the player is no longer valid and active, then notify listeners
      * that the data was updated. Does not convert to resume state if the player is still valid, or
@@ -1391,8 +1395,9 @@
             if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
             mediaEntries.put(key, removed)
             notifyMediaDataLoaded(key, key, removed)
-        } else if (removed.active) {
-            // This player was still active - it didn't last long enough to time out: remove
+        } else if (removed.active && !isAbleToResume(removed)) {
+            // This player was still active - it didn't last long enough to time out,
+            // and its app doesn't normally support resume: remove
             if (DEBUG) Log.d(TAG, "Removing still-active player $key")
             notifyMediaDataRemoved(key)
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b0ad6a1..37538a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -500,8 +500,10 @@
 
     private fun updateTextColorFromRegionSampler() {
         smartspaceViews.forEach {
-            val textColor = regionSamplers.getValue(it).currentForegroundColor()
-            it.setPrimaryTextColor(textColor)
+            val textColor = regionSamplers.get(it)?.currentForegroundColor()
+            if (textColor != null) {
+                it.setPrimaryTextColor(textColor)
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
new file mode 100644
index 0000000..0e14591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class ConditionalRestarterTest : SysuiTestCase() {
+    private lateinit var restarter: ConditionalRestarter
+
+    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+    val restartDelayMs = 0L
+    val dispatcher = StandardTestDispatcher()
+    val testScope = TestScope(dispatcher)
+
+    val conditionA = FakeCondition()
+    val conditionB = FakeCondition()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        restarter =
+            ConditionalRestarter(
+                systemExitRestarter,
+                setOf(conditionA, conditionB),
+                restartDelayMs,
+                testScope,
+                dispatcher
+            )
+    }
+
+    @Test
+    fun restart_ImmediatelySatisfied() =
+        testScope.runTest {
+            conditionA.canRestart = true
+            conditionB.canRestart = true
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    @Test
+    fun restart_WaitsForConditionA() =
+        testScope.runTest {
+            conditionA.canRestart = false
+            conditionB.canRestart = true
+
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            conditionA.canRestart = true
+            conditionA.retryFn?.invoke()
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    @Test
+    fun restart_WaitsForConditionB() =
+        testScope.runTest {
+            conditionA.canRestart = true
+            conditionB.canRestart = false
+
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            conditionB.canRestart = true
+            conditionB.retryFn?.invoke()
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    @Test
+    fun restart_WaitsForAllConditions() =
+        testScope.runTest {
+            conditionA.canRestart = true
+            conditionB.canRestart = false
+
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            // B becomes true, but A is now false
+            conditionA.canRestart = false
+            conditionB.canRestart = true
+            conditionB.retryFn?.invoke()
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            conditionA.canRestart = true
+            conditionA.retryFn?.invoke()
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    class FakeCondition : ConditionalRestarter.Condition {
+        var retryFn: (() -> Unit)? = null
+        var canRestart = false
+
+        override fun canRestartNow(retryFn: () -> Unit): Boolean {
+            this.retryFn = retryFn
+
+            return canRestart
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
deleted file mode 100644
index 6060afe..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.flags
-
-import android.test.suitebuilder.annotation.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-/**
- * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
- */
-@SmallTest
-class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
-    private lateinit var restarter: FeatureFlagsReleaseRestarter
-
-    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock private lateinit var batteryController: BatteryController
-    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
-    private val executor = FakeExecutor(FakeSystemClock())
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        restarter =
-            FeatureFlagsReleaseRestarter(
-                wakefulnessLifecycle,
-                batteryController,
-                executor,
-                systemExitRestarter
-            )
-    }
-
-    @Test
-    fun testRestart_ScheduledWhenReady() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun testRestart_RestartsWhenIdle() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        restarter.restartSystemUI("Restart for test")
-        verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
-        executor.advanceClockToLast()
-        executor.runAllReady()
-        verify(systemExitRestarter).restartSystemUI(any())
-    }
-
-    @Test
-    fun testRestart_NotScheduledWhenAwake() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(0)
-    }
-
-    @Test
-    fun testRestart_NotScheduledWhenNotPluggedIn() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(false)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(0)
-    }
-
-    @Test
-    fun testRestart_NotDoubleSheduled() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun testWakefulnessLifecycle_CanRestart() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-
-        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
-        verify(wakefulnessLifecycle).addObserver(captor.capture())
-
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-
-        captor.value.onFinishedGoingToSleep()
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun testBatteryController_CanRestart() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(false)
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-
-        val captor =
-            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
-        verify(batteryController).addCallback(captor.capture())
-
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        captor.value.onBatteryLevelChanged(0, true, true)
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
new file mode 100644
index 0000000..647b05a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.BatteryController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class PluggedInConditionTest : SysuiTestCase() {
+    private lateinit var condition: PluggedInCondition
+
+    @Mock private lateinit var batteryController: BatteryController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        condition = PluggedInCondition(batteryController)
+    }
+
+    @Test
+    fun testCondition_unplugged() {
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+
+        assertThat(condition.canRestartNow({})).isFalse()
+    }
+
+    @Test
+    fun testCondition_pluggedIn() {
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(condition.canRestartNow({})).isTrue()
+    }
+
+    @Test
+    fun testCondition_invokesRetry() {
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+        var retried = false
+        val retryFn = { retried = true }
+
+        // No restart yet, but we do register a listener now.
+        assertThat(condition.canRestartNow(retryFn)).isFalse()
+        val captor =
+            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+        verify(batteryController).addCallback(captor.capture())
+
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        captor.value.onBatteryLevelChanged(0, true, true)
+        assertThat(retried).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 686782f..f7a773e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,12 +20,11 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
 import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -34,37 +33,45 @@
  * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
  */
 @SmallTest
-class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
-    private lateinit var restarter: FeatureFlagsDebugRestarter
+class ScreenIdleConditionTest : SysuiTestCase() {
+    private lateinit var condition: ScreenIdleCondition
 
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+        condition = ScreenIdleCondition(wakefulnessLifecycle)
     }
 
     @Test
-    fun testRestart_ImmediateWhenAsleep() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        restarter.restartSystemUI("Restart for test")
-        verify(systemExitRestarter).restartSystemUI(any())
-    }
-
-    @Test
-    fun testRestart_WaitsForSceenOff() {
+    fun testCondition_awake() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
 
-        restarter.restartSystemUI("Restart for test")
-        verify(systemExitRestarter, never()).restartSystemUI(any())
+        assertThat(condition.canRestartNow {}).isFalse()
+    }
 
+    @Test
+    fun testCondition_asleep() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+        assertThat(condition.canRestartNow {}).isTrue()
+    }
+
+    @Test
+    fun testCondition_invokesRetry() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+        var retried = false
+        val retryFn = { retried = true }
+
+        // No restart yet, but we do register a listener now.
+        assertThat(condition.canRestartNow(retryFn)).isFalse()
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
 
-        captor.value.onFinishedGoingToSleep()
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
 
-        verify(systemExitRestarter).restartSystemUI(any())
+        captor.value.onFinishedGoingToSleep()
+        assertThat(retried).isTrue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index ab0669a..d428db7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -2031,7 +2031,7 @@
     }
 
     @Test
-    fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+    fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
@@ -2051,6 +2051,40 @@
     }
 
     @Test
+    fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions and that does allow resumption is added,
+        addNotificationAndLoad()
+        val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+        // And then the session is destroyed without timing out first
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)