[DO NOT MERGE] Support toggling resumption in Settings

Moved setting so we can use Tunable:
adb shell settings put secure qs_media_resumption 1

If setting is changed, removes all existing resume players

Bug: 154039093
Test: manual
Test: atest SettingsProviderTest
Test: atest com.android.systemui.media

Change-Id: Iad056fbad4520cfe762d9e9f5ed62d38ea1117b1
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1fafbd3..4abb2c59 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1898,6 +1898,15 @@
             "android.settings.ACTION_DEVICE_CONTROLS_SETTINGS";
 
     /**
+     * Activity Action: Show media control settings
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MEDIA_CONTROLS_SETTINGS =
+            "android.settings.ACTION_MEDIA_CONTROLS_SETTINGS";
+
+    /**
      * Activity Action: Show a dialog with disabled by policy message.
      * <p> If an user action is disabled by policy, this dialog can be triggered to let
      * the user know about this.
@@ -8911,6 +8920,15 @@
         public static final String PEOPLE_STRIP = "people_strip";
 
         /**
+         * Whether or not to enable media resumption
+         * When enabled, media controls in quick settings will populate on boot and persist if
+         * resumable via a MediaBrowserService.
+         * @see Settings.Global#SHOW_MEDIA_ON_QUICK_SETTINGS
+         * @hide
+         */
+        public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption";
+
+        /**
          * Controls if window magnification is enabled.
          * @hide
          */
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 997829e..69b32c2 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2678,4 +2678,9 @@
     // CATEGORY: SETTINGS
     // OS: R
     DEVICE_CONTROLS_SETTINGS = 1844;
+
+    // OPEN: Settings > Sound > Media
+    // CATEGORY: SETTINGS
+    // OS: R
+    MEDIA_CONTROLS_SETTINGS = 1845;
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c04a1ba..d05e6e1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -164,7 +164,8 @@
         Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
         Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
         Settings.Secure.PEOPLE_STRIP,
+        Settings.Secure.MEDIA_CONTROLS_RESUME,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 76746e5..fa810bd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -243,6 +243,7 @@
         VALIDATORS.put(Secure.DISPLAY_DENSITY_FORCED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b95af26..b5a2435 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2784,10 +2784,16 @@
          recommended controls [CHAR_LIMIT=60] -->
     <string name="controls_seeding_in_progress">Loading recommendations</string>
 
-    <!-- Close the controls associated with a specific media session [CHAR_LIMIT=NONE] -->
-    <string name="controls_media_close_session">Close this media session</string>
+    <!-- Title for media controls [CHAR_LIMIT=50] -->
+    <string name="controls_media_title">Media</string>
+    <!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_close_session">Hide the current session.</string>
+    <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
+    <string name="controls_media_dismiss_button">Hide</string>
     <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
     <string name="controls_media_resume">Resume</string>
+    <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
+    <string name="controls_media_settings_button">Settings</string>
 
     <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
     <string name="controls_error_timeout">Inactive, check app</string>
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 1ec285a..c59a548 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
@@ -96,7 +95,7 @@
     dumpManager: DumpManager,
     mediaTimeoutListener: MediaTimeoutListener,
     mediaResumeListener: MediaResumeListener,
-    private val useMediaResumption: Boolean,
+    private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
@@ -149,18 +148,9 @@
         mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
             setTimedOut(token, timedOut) }
         addListener(mediaTimeoutListener)
-        if (useMediaResumption) {
-            mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription,
-                resumeAction: Runnable, token: MediaSession.Token, appName: String,
-                appIntent: PendingIntent, packageName: String ->
-                addResumptionControls(desc, resumeAction, token, appName, appIntent, packageName)
-            }
-            mediaResumeListener.resumeComponentFoundCallback = { key: String, action: Runnable? ->
-                mediaEntries.get(key)?.resumeAction = action
-                mediaEntries.get(key)?.hasCheckedForResume = true
-            }
-            addListener(mediaResumeListener)
-        }
+
+        mediaResumeListener.setManager(this)
+        addListener(mediaResumeListener)
 
         val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED)
         broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL)
@@ -223,7 +213,14 @@
         }
     }
 
-    private fun addResumptionControls(
+    fun setResumeAction(key: String, action: Runnable?) {
+        mediaEntries.get(key)?.let {
+            it.resumeAction = action
+            it.hasCheckedForResume = true
+        }
+    }
+
+    fun addResumptionControls(
         desc: MediaDescription,
         action: Runnable,
         token: MediaSession.Token,
@@ -233,7 +230,8 @@
     ) {
         // Resume controls don't have a notification key, so store by package name instead
         if (!mediaEntries.containsKey(packageName)) {
-            val resumeData = LOADING.copy(packageName = packageName, resumeAction = action)
+            val resumeData = LOADING.copy(packageName = packageName, resumeAction = action,
+                hasCheckedForResume = true)
             mediaEntries.put(packageName, resumeData)
         }
         backgroundExecutor.execute {
@@ -301,6 +299,8 @@
     ) {
         if (TextUtils.isEmpty(desc.title)) {
             Log.e(TAG, "Description incomplete")
+            // Delete the placeholder entry
+            mediaEntries.remove(packageName)
             return
         }
 
@@ -323,7 +323,8 @@
             onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
                     packageName, token, appIntent, device = null, active = false,
-                    resumeAction = resumeAction))
+                    resumeAction = resumeAction, notificationKey = packageName,
+                    hasCheckedForResume = true))
         }
     }
 
@@ -432,11 +433,12 @@
         }
 
         val resumeAction: Runnable? = mediaEntries.get(key)?.resumeAction
+        val hasCheckedForResume = mediaEntries.get(key)?.hasCheckedForResume == true
         foregroundExecutor.execute {
             onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
                     song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
                     notif.contentIntent, null, active = true, resumeAction = resumeAction,
-                    notificationKey = key))
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume))
         }
     }
 
@@ -540,7 +542,7 @@
             val data = mediaEntries.remove(key)!!
             val resumeAction = getResumeMediaAction(data.resumeAction!!)
             val updated = data.copy(token = null, actions = listOf(resumeAction),
-                actionsToShowInCompact = listOf(0))
+                actionsToShowInCompact = listOf(0), active = false)
             mediaEntries.put(data.packageName, updated)
             // Notify listeners of "new" controls
             val listenersCopy = listeners.toSet()
@@ -563,20 +565,33 @@
      */
     fun hasActiveMedia() = mediaEntries.any { it.value.active }
 
-    fun isActive(token: MediaSession.Token?): Boolean {
-        if (token == null) {
-            return false
-        }
-        val controller = mediaControllerFactory.create(token)
-        val state = controller?.playbackState?.state
-        return state != null && NotificationMediaManager.isActiveState(state)
-    }
-
     /**
-     * Are there any media entries, including resume controls?
+     * Are there any media entries we should display?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
      */
     fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()
 
+    fun setMediaResumptionEnabled(isEnabled: Boolean) {
+        if (useMediaResumption == isEnabled) {
+            return
+        }
+
+        useMediaResumption = isEnabled
+
+        if (!useMediaResumption) {
+            // Remove any existing resume controls
+            val listenersCopy = listeners.toSet()
+            val filtered = mediaEntries.filter { !it.value.active }
+            filtered.forEach {
+                mediaEntries.remove(it.key)
+                listenersCopy.forEach { listener ->
+                    listener.onMediaDataRemoved(it.key)
+                }
+            }
+        }
+    }
+
     interface Listener {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index e8a4b1e..0cc1e7b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.media
 
-import android.app.PendingIntent
 import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
@@ -25,12 +24,13 @@
 import android.content.pm.PackageManager
 import android.media.MediaDescription
 import android.media.session.MediaController
-import android.media.session.MediaSession
 import android.os.UserHandle
+import android.provider.Settings
 import android.service.media.MediaBrowserService
 import android.util.Log
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.Executor
@@ -46,21 +46,14 @@
 class MediaResumeListener @Inject constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
-    @Background private val backgroundExecutor: Executor
+    @Background private val backgroundExecutor: Executor,
+    private val tunerService: TunerService
 ) : MediaDataManager.Listener {
 
-    private val useMediaResumption: Boolean = Utils.useMediaResumption(context)
+    private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
     private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
 
-    lateinit var addTrackToResumeCallback: (
-        MediaDescription,
-        Runnable,
-        MediaSession.Token,
-        String,
-        PendingIntent,
-        String
-    ) -> Unit
-    lateinit var resumeComponentFoundCallback: (String, Runnable?) -> Unit
+    private lateinit var mediaDataManager: MediaDataManager
 
     private var mediaBrowser: ResumeMediaBrowser? = null
     private var currentUserId: Int
@@ -96,8 +89,8 @@
             }
 
             Log.d(TAG, "Adding resume controls $desc")
-            addTrackToResumeCallback(desc, resumeAction, token, appName.toString(), appIntent,
-                component.packageName)
+            mediaDataManager.addResumptionControls(desc, resumeAction, token, appName.toString(),
+                appIntent, component.packageName)
         }
     }
 
@@ -113,6 +106,18 @@
         }
     }
 
+    fun setManager(manager: MediaDataManager) {
+        mediaDataManager = manager
+
+        // Add listener for resumption setting changes
+        tunerService.addTunable(object : TunerService.Tunable {
+            override fun onTuningChanged(key: String?, newValue: String?) {
+                useMediaResumption = Utils.useMediaResumption(context)
+                mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
+            }
+        }, Settings.Secure.MEDIA_CONTROLS_RESUME)
+    }
+
     private fun loadSavedComponents() {
         // Make sure list is empty (if we switched users)
         resumeComponents.clear()
@@ -165,7 +170,7 @@
                     }
                 } else {
                     // No service found
-                    resumeComponentFoundCallback(key, null)
+                    mediaDataManager.setResumeAction(key, null)
                 }
             }
         }
@@ -182,7 +187,7 @@
                 object : ResumeMediaBrowser.Callback() {
                     override fun onConnected() {
                         Log.d(TAG, "yes we can resume with $componentName")
-                        resumeComponentFoundCallback(key, getResumeAction(componentName))
+                        mediaDataManager.setResumeAction(key, getResumeAction(componentName))
                         updateResumptionList(componentName)
                         mediaBrowser?.disconnect()
                         mediaBrowser = null
@@ -190,7 +195,7 @@
 
                     override fun onError() {
                         Log.e(TAG, "Cannot resume with $componentName")
-                        resumeComponentFoundCallback(key, null)
+                        mediaDataManager.setResumeAction(key, null)
                         mediaBrowser?.disconnect()
                         mediaBrowser = null
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 6462f07..1842564 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -30,8 +30,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.systemui.util.Utils;
-
 import java.util.List;
 
 /**
@@ -46,7 +44,6 @@
     public static final String DELIMITER = ":";
 
     private static final String TAG = "ResumeMediaBrowser";
-    private boolean mIsEnabled = false;
     private final Context mContext;
     private final Callback mCallback;
     private MediaBrowser mMediaBrowser;
@@ -59,7 +56,6 @@
      * @param componentName Component name of the MediaBrowserService this browser will connect to
      */
     public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
-        mIsEnabled = Utils.useMediaResumption(context);
         mContext = context;
         mCallback = callback;
         mComponentName = componentName;
@@ -74,9 +70,6 @@
      * ResumeMediaBrowser#disconnect will be called automatically with this function.
      */
     public void findRecentMedia() {
-        if (!mIsEnabled) {
-            return;
-        }
         Log.d(TAG, "Connecting to " + mComponentName);
         disconnect();
         Bundle rootHints = new Bundle();
@@ -186,9 +179,6 @@
      * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void restart() {
-        if (!mIsEnabled) {
-            return;
-        }
         disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
@@ -250,9 +240,6 @@
      * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void testConnection() {
-        if (!mIsEnabled) {
-            return;
-        }
         disconnect();
         final MediaBrowser.ConnectionCallback connectionCallback =
                 new MediaBrowser.ConnectionCallback() {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 248bdc8..9ad2aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -62,7 +62,8 @@
     // shouldn't be reset with tuner settings.
     private static final String[] RESET_BLACKLIST = new String[] {
             QSTileHost.TILES_SETTING,
-            Settings.Secure.DOZE_ALWAYS_ON
+            Settings.Secure.DOZE_ALWAYS_ON,
+            Settings.Secure.MEDIA_CONTROLS_RESUME
     };
 
     private final Observer mObserver = new Observer();
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 5c9db54..e5f30cf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -139,7 +139,8 @@
      * Off by default, but can be enabled by setting to 1
      */
     public static boolean useMediaResumption(Context context) {
-        int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_resumption", 0);
+        int flag = Settings.Secure.getInt(context.getContentResolver(),
+                Settings.Secure.MEDIA_CONTROLS_RESUME, 1);
         return useQsMediaPlayer(context) && flag > 0;
     }
 }