diff options
10 files changed, 105 insertions, 65 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3055c809a3b7..c0a4635c33b4 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1898,6 +1898,15 @@ public final class Settings { "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 final class Settings { 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 997829eacf96..69b32c264d3d 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2678,4 +2678,9 @@ enum PageId { // 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 c04a1ba689b9..d05e6e16bc1a 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 @@ public class SecureSettings { 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 76746e5488b6..fa810bdf3a4e 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 @@ public class SecureSettingsValidators { 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 ebbf8f298944..0e3fa1e48695 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 1ec285a8f561..c59a548c8db4 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.broadcast.BroadcastDispatcher 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 @@ class MediaDataManager( dumpManager: DumpManager, mediaTimeoutListener: MediaTimeoutListener, mediaResumeListener: MediaResumeListener, - private val useMediaResumption: Boolean, + private var useMediaResumption: Boolean, private val useQsMediaPlayer: Boolean ) : Dumpable { @@ -149,18 +148,9 @@ class MediaDataManager( 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 @@ class MediaDataManager( } } - 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 @@ class MediaDataManager( ) { // 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 @@ class MediaDataManager( ) { if (TextUtils.isEmpty(desc.title)) { Log.e(TAG, "Description incomplete") + // Delete the placeholder entry + mediaEntries.remove(packageName) return } @@ -323,7 +323,8 @@ class MediaDataManager( 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 @@ class MediaDataManager( } 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 @@ class MediaDataManager( 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 @@ class MediaDataManager( */ 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 e8a4b1e46fec..0cc1e7bb1b56 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.IntentFilter 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 @@ private const val MEDIA_PREFERENCE_KEY = "browser_components_" 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 @@ class MediaResumeListener @Inject constructor( } 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 @@ class MediaResumeListener @Inject constructor( } } + 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 @@ class MediaResumeListener @Inject constructor( } } else { // No service found - resumeComponentFoundCallback(key, null) + mediaDataManager.setResumeAction(key, null) } } } @@ -182,7 +187,7 @@ class MediaResumeListener @Inject constructor( 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 @@ class MediaResumeListener @Inject constructor( 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 6462f072bc74..1842564a4574 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.service.media.MediaBrowserService; import android.text.TextUtils; import android.util.Log; -import com.android.systemui.util.Utils; - import java.util.List; /** @@ -46,7 +44,6 @@ public class ResumeMediaBrowser { 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 @@ public class ResumeMediaBrowser { * @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 @@ public class ResumeMediaBrowser { * 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 @@ public class ResumeMediaBrowser { * 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 @@ public class ResumeMediaBrowser { * 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 248bdc870418..9ad2aa257aa0 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 @@ public class TunerServiceImpl extends TunerService { // 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 5c9db54a0f00..e5f30cf63ac3 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 @@ public class Utils { * 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; } } |