[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
This commit is contained in:
@ -1897,6 +1897,15 @@ public final class Settings {
|
||||
public static final String ACTION_DEVICE_CONTROLS_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
|
||||
@ -8910,6 +8919,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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user