Merge "Use compat framework to gate session actions" into tm-dev am: 96a7b9ee75

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17158683

Change-Id: I88c7bdc090f9e936d8a214ee70539e6a04572dd3
This commit is contained in:
TreeHugger Robot
2022-03-15 17:10:56 +00:00
committed by Automerger Merge Worker
10 changed files with 68 additions and 34 deletions

View File

@@ -24,6 +24,9 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi; import android.annotation.SystemApi;
import android.annotation.SystemService; import android.annotation.SystemService;
import android.annotation.TestApi; import android.annotation.TestApi;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@@ -39,6 +42,7 @@ import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Pair; import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.view.View; import android.view.View;
@@ -520,6 +524,27 @@ public class StatusBarManager {
private final Map<NearbyMediaDevicesProvider, NearbyMediaDevicesProviderWrapper> private final Map<NearbyMediaDevicesProvider, NearbyMediaDevicesProviderWrapper>
nearbyMediaDevicesProviderMap = new HashMap<>(); nearbyMediaDevicesProviderMap = new HashMap<>();
/**
* Media controls based on {@link android.app.Notification.MediaStyle} notifications will have
* actions based on the media session's {@link android.media.session.PlaybackState}, rather than
* the notification's actions.
*
* These actions will be:
* - Play/Pause (depending on whether the current state is a playing state)
* - Previous (if declared), or a custom action if the slot is not reserved with
* {@code SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV}
* - Next (if declared), or a custom action if the slot is not reserved with
* {@code SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT}
* - Custom action
* - Custom action
*
* @see androidx.media.utils.MediaConstants#SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
* @see androidx.media.utils.MediaConstants#SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L;
@UnsupportedAppUsage @UnsupportedAppUsage
private Context mContext; private Context mContext;
private IStatusBarService mService; private IStatusBarService mService;
@@ -1127,6 +1152,20 @@ public class StatusBarManager {
} }
} }
/**
* Checks whether the given package should use session-based actions for its media controls.
*
* @param packageName App posting media controls
* @param userId Current user ID
* @return true if the app supports session actions
*
* @hide
*/
public static boolean useMediaSessionActionsForApp(String packageName, int userId) {
UserHandle handle = UserHandle.getUserHandleForUid(userId);
return CompatChanges.isChangeEnabled(MEDIA_CONTROL_SESSION_ACTIONS, packageName, handle);
}
/** @hide */ /** @hide */
public static String windowStateToString(int state) { public static String windowStateToString(int state) {
if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING"; if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";

View File

@@ -76,5 +76,8 @@
<permission name="android.permission.FORCE_STOP_PACKAGES" /> <permission name="android.permission.FORCE_STOP_PACKAGES" />
<permission name="android.permission.ACCESS_FPS_COUNTER" /> <permission name="android.permission.ACCESS_FPS_COUNTER" />
<permission name="android.permission.CHANGE_CONFIGURATION" /> <permission name="android.permission.CHANGE_CONFIGURATION" />
<permission name="android.permission.LOG_COMPAT_CHANGE" />
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<permission name="android.permission.READ_DEVICE_CONFIG" />
</privapp-permissions> </privapp-permissions>
</permissions> </permissions>

View File

@@ -314,6 +314,11 @@
<!-- To change system captions state --> <!-- To change system captions state -->
<uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" /> <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
<!-- Compat framework -->
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />

View File

@@ -145,7 +145,7 @@ public class Flags {
/***************************************/ /***************************************/
// 900 - media // 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true); public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true); public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, true); public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, true);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true); public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true); public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);

View File

@@ -137,7 +137,6 @@ public class MediaControlPanel {
private MediaCarouselController mMediaCarouselController; private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private final FalsingManager mFalsingManager; private final FalsingManager mFalsingManager;
private final MediaFlags mMediaFlags;
// Used for swipe-to-dismiss logging. // Used for swipe-to-dismiss logging.
protected boolean mIsImpressed = false; protected boolean mIsImpressed = false;
@@ -156,7 +155,7 @@ public class MediaControlPanel {
Lazy<MediaDataManager> lazyMediaDataManager, Lazy<MediaDataManager> lazyMediaDataManager,
MediaOutputDialogFactory mediaOutputDialogFactory, MediaOutputDialogFactory mediaOutputDialogFactory,
MediaCarouselController mediaCarouselController, MediaCarouselController mediaCarouselController,
FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) { FalsingManager falsingManager, SystemClock systemClock) {
mContext = context; mContext = context;
mBackgroundExecutor = backgroundExecutor; mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter; mActivityStarter = activityStarter;
@@ -167,7 +166,6 @@ public class MediaControlPanel {
mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaOutputDialogFactory = mediaOutputDialogFactory;
mMediaCarouselController = mediaCarouselController; mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager; mFalsingManager = falsingManager;
mMediaFlags = mediaFlags;
mSystemClock = systemClock; mSystemClock = systemClock;
loadDimens(); loadDimens();
@@ -506,9 +504,8 @@ public class MediaControlPanel {
List<MediaAction> actionIcons = data.getActions(); List<MediaAction> actionIcons = data.getActions();
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact(); List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
// If the session actions flag is enabled, but we're still using the regular layout, use // If we got session actions, use those instead
// the session actions anyways if (data.getSemanticActions() != null) {
if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
MediaButton semanticActions = data.getSemanticActions(); MediaButton semanticActions = data.getSemanticActions();
actionIcons = new ArrayList<MediaAction>(); actionIcons = new ArrayList<MediaAction>();

View File

@@ -610,7 +610,8 @@ class MediaDataManager(
var actionIcons: List<MediaAction> = emptyList() var actionIcons: List<MediaAction> = emptyList()
var actionsToShowCollapsed: List<Int> = emptyList() var actionsToShowCollapsed: List<Int> = emptyList()
var semanticActions: MediaButton? = null var semanticActions: MediaButton? = null
if (mediaFlags.areMediaSessionActionsEnabled() && mediaController.playbackState != null) { if (mediaFlags.areMediaSessionActionsEnabled(sbn.packageName, sbn.user.identifier) &&
mediaController.playbackState != null) {
semanticActions = createActionsFromState(sbn.packageName, mediaController) semanticActions = createActionsFromState(sbn.packageName, mediaController)
} else { } else {
val actions = createActionsFromNotification(sbn) val actions = createActionsFromNotification(sbn)
@@ -726,7 +727,7 @@ class MediaDataManager(
} }
} }
// Finally, assign the remaining button slots: C A play/pause B D // Finally, assign the remaining button slots: play/pause A B C D
// A = previous, else custom action (if not reserved) // A = previous, else custom action (if not reserved)
// B = next, else custom action (if not reserved) // B = next, else custom action (if not reserved)
// C and D are always custom actions // C and D are always custom actions

View File

@@ -16,6 +16,7 @@
package com.android.systemui.media package com.android.systemui.media
import android.app.StatusBarManager
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags
@@ -26,16 +27,17 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** /**
* Check whether media control actions should be based on PlaybackState instead of notification * Check whether media control actions should be based on PlaybackState instead of notification
*/ */
fun areMediaSessionActionsEnabled(): Boolean { fun areMediaSessionActionsEnabled(packageName: String, userId: Int): Boolean {
return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) val enabled = StatusBarManager.useMediaSessionActionsForApp(packageName, userId)
// Allow global override with flag
return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
} }
/** /**
* Check whether media controls should use the new session-based layout * Check whether media controls should use the new session-based layout
*/ */
fun useMediaSessionLayout(): Boolean { fun useMediaSessionLayout(): Boolean {
return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) && return featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
} }
/** /**

View File

@@ -37,7 +37,6 @@ import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import javax.inject.Provider import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
private val DATA = MediaData( private val DATA = MediaData(
userId = -1, userId = -1,
@@ -83,7 +82,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Before @Before
fun setup() { fun setup() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
mediaCarouselController = MediaCarouselController( mediaCarouselController = MediaCarouselController(
context, context,
mediaControlPanelFactory, mediaControlPanelFactory,

View File

@@ -101,7 +101,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController @Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var mediaFlags: MediaFlags
private lateinit var appIcon: ImageView private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView private lateinit var albumView: ImageView
private lateinit var titleText: TextView private lateinit var titleText: TextView
@@ -147,7 +146,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player = MediaControlPanel(context, bgExecutor, activityStarter, broadcastSender, player = MediaControlPanel(context, bgExecutor, activityStarter, broadcastSender,
mediaViewController, seekBarViewModel, Lazy { mediaDataManager }, mediaViewController, seekBarViewModel, Lazy { mediaDataManager },
mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock) mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
whenever(seekBarViewModel.progress).thenReturn(seekBarData) whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Set up mock views for the players // Set up mock views for the players
@@ -215,9 +214,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
device = device, device = device,
active = true, active = true,
resumeAction = null) resumeAction = null)
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
} }
/** /**
@@ -295,9 +291,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test @Test
fun bindSemanticActionsOldLayout() { fun bindSemanticActionsOldLayout() {
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val semanticActions = MediaButton( val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"), playOrPause = MediaAction(icon, Runnable {}, "play"),
@@ -332,9 +325,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test @Test
fun bindSemanticActionsNewLayout() { fun bindSemanticActionsNewLayout() {
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val semanticActions = MediaButton( val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"), playOrPause = MediaAction(icon, Runnable {}, "play"),
@@ -381,9 +371,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test @Test
fun bindNotificationActionsNewLayout() { fun bindNotificationActionsNewLayout() {
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val actions = listOf( val actions = listOf(
MediaAction(icon, Runnable {}, "previous"), MediaAction(icon, Runnable {}, "previous"),

View File

@@ -26,6 +26,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.tuner.TunerService import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
@@ -37,6 +38,7 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito
@@ -167,7 +169,7 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA) whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem)) whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), anyInt())).thenReturn(false)
} }
@After @After
@@ -594,7 +596,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test @Test
fun testPlaybackActions_noState_usesNotification() { fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action" val desc = "Notification Action"
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), anyInt())).thenReturn(true)
whenever(controller.playbackState).thenReturn(null) whenever(controller.playbackState).thenReturn(null)
val notifWithAction = SbnBuilder().run { val notifWithAction = SbnBuilder().run {
@@ -621,7 +623,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test @Test
fun testPlaybackActions_hasPrevNext() { fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), anyInt())).thenReturn(true)
val stateActions = PlaybackState.ACTION_PLAY or val stateActions = PlaybackState.ACTION_PLAY or
PlaybackState.ACTION_SKIP_TO_PREVIOUS or PlaybackState.ACTION_SKIP_TO_PREVIOUS or
PlaybackState.ACTION_SKIP_TO_NEXT PlaybackState.ACTION_SKIP_TO_NEXT
@@ -669,7 +671,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test @Test
fun testPlaybackActions_noPrevNext_usesCustom() { fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5") val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), anyInt())).thenReturn(true)
val stateActions = PlaybackState.ACTION_PLAY val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder() val stateBuilder = PlaybackState.Builder()
.setActions(stateActions) .setActions(stateActions)
@@ -707,7 +709,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test @Test
fun testPlaybackActions_reservedSpace() { fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), anyInt())).thenReturn(true)
val stateActions = PlaybackState.ACTION_PLAY val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder() val stateBuilder = PlaybackState.Builder()
.setActions(stateActions) .setActions(stateActions)