[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:
Beth Thibodeau
2020-06-09 23:36:17 -04:00
parent a7143d54c7
commit c1bc307bce
10 changed files with 105 additions and 65 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -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,

View File

@ -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>

View File

@ -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 {
/**

View File

@ -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
}

View File

@ -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() {

View File

@ -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();

View File

@ -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;
}
}