Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bbe92d44 authored by Beth Thibodeau's avatar Beth Thibodeau
Browse files

Add flag to use PlaybackState actions in media controls

If flag is enabled, media control actions will be created from the
PlaybackState following the pattern: C A (play/pause) B D
A = previous, or custom action (if not reserved)
B = next, or custom action (if not reserved)
C and D = custom actions

Previous and next slots can be reserved by the app sending the extra
SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV (or NEXT), same as
Android Auto. This will result in a blank space in the UI.

If the flag is not enabled, or the app doesn't include a PlaybackState,
notification actions will be used instead (no change in behavior)

Flag can be enabled using:
adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id 901 --ez value 1

Fixes: 203800354
Test: atest com.android.systemui.media
Test: manual, toggle flag
Change-Id: Iba20f8ab61c9057cbbd1849bb262da693edd6cb5
parent 040d9338
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -2164,6 +2164,15 @@
    <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
    <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>

    <!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
    <string name="controls_media_button_play">Play</string>
    <!-- Description for button in media controls. Pressing button pauses playback [CHAR_LIMIT=NONE] -->
    <string name="controls_media_button_pause">Pause</string>
    <!-- Description for button in media controls. Pressing button goes to previous track [CHAR_LIMIT=NONE] -->
    <string name="controls_media_button_prev">Previous track</string>
    <!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
    <string name="controls_media_button_next">Next track</string>

    <!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
    <string name="controls_media_smartspace_rec_title">Play</string>
    <!-- Description for Smartspace recommendation card within media controls [CHAR_LIMIT=NONE] -->
+1 −0
Original line number Diff line number Diff line
@@ -121,6 +121,7 @@ public class Flags {
    /***************************************/
    // 900 - media
    public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
    public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);

    // Pay no attention to the reflection behind the curtain.
    // ========================== Curtain ==========================
+17 −2
Original line number Diff line number Diff line
@@ -862,8 +862,23 @@ class MediaCarouselController @Inject constructor(

@VisibleForTesting
internal object MediaPlayerData {
    private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
            emptyList(), emptyList(), "INVALID", null, null, null, true, null)
    private val EMPTY = MediaData(
            userId = -1,
            initialized = false,
            backgroundColor = 0,
            app = null,
            appIcon = null,
            artist = null,
            song = null,
            artwork = null,
            actions = emptyList(),
            actionsToShowInCompact = emptyList(),
            packageName = "INVALID",
            token = null,
            clickIntent = null,
            device = null,
            active = true,
            resumeAction = null)
    // Whether should prioritize Smartspace card.
    internal var shouldPrioritizeSs: Boolean = false
        private set
+52 −21
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

@@ -122,6 +123,8 @@ public class MediaControlPanel {
    private MediaCarouselController mMediaCarouselController;
    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
    private final FalsingManager mFalsingManager;
    private final MediaFlags mMediaFlags;

    // Used for swipe-to-dismiss logging.
    protected boolean mIsImpressed = false;
    private SystemClock mSystemClock;
@@ -138,7 +141,7 @@ public class MediaControlPanel {
            SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
            KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
            mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
            FalsingManager falsingManager, SystemClock systemClock) {
            FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
        mContext = context;
        mBackgroundExecutor = backgroundExecutor;
        mActivityStarter = activityStarter;
@@ -149,8 +152,8 @@ public class MediaControlPanel {
        mMediaOutputDialogFactory = mediaOutputDialogFactory;
        mMediaCarouselController = mediaCarouselController;
        mFalsingManager = falsingManager;
        mMediaFlags = mediaFlags;
        mSystemClock = systemClock;

        loadDimens();

        mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -426,14 +429,34 @@ public class MediaControlPanel {
        deviceName.setText(deviceString);
        seamlessView.setContentDescription(deviceString);

        // Media action buttons
        List<MediaAction> actionIcons = data.getActions();
        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
        // Media controls

        if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
            // Use PlaybackState actions instead
            MediaButton semanticActions = data.getSemanticActions();

            actionIcons = new ArrayList<MediaAction>();
            actionIcons.add(semanticActions.getStartCustom());
            actionIcons.add(semanticActions.getPrevOrCustom());
            actionIcons.add(semanticActions.getPlayOrPause());
            actionIcons.add(semanticActions.getNextOrCustom());
            actionIcons.add(semanticActions.getEndCustom());

            actionsWhenCollapsed = new ArrayList<Integer>();
            actionsWhenCollapsed.add(1);
            actionsWhenCollapsed.add(2);
            actionsWhenCollapsed.add(3);
        }

        int i = 0;
        List<MediaAction> actionIcons = data.getActions();
        for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
            int actionId = ACTION_IDS[i];
            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
            final ImageButton button = mPlayerViewHolder.getAction(actionId);
            MediaAction mediaAction = actionIcons.get(i);
            if (mediaAction != null) {
                button.setImageIcon(mediaAction.getIcon());
                button.setContentDescription(mediaAction.getContentDescription());
                Runnable action = mediaAction.getAction();
@@ -450,9 +473,17 @@ public class MediaControlPanel {
                        }
                    });
                }
            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
                setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
            } else {
                button.setImageIcon(null);
                button.setContentDescription(null);
                button.setEnabled(false);
                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
                // for expanded layout, set as INVISIBLE so that we still reserve space in the UI
                expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
                expandedSet.setAlpha(actionId, 0.0f);
            }
        }

        // Hide any unused buttons
+32 −1
Original line number Diff line number Diff line
@@ -47,13 +47,18 @@ data class MediaData(
     */
    val artwork: Icon?,
    /**
     * List of actions that can be performed on the player: prev, next, play, pause, etc.
     * List of generic action buttons for the media player, based on notification actions
     */
    val actions: List<MediaAction>,
    /**
     * Same as above, but shown on smaller versions of the player, like in QQS or keyguard.
     */
    val actionsToShowInCompact: List<Int>,
    /**
     * Semantic actions buttons, based on the PlaybackState of the media session.
     * If present, these actions will be preferred in the UI over [actions]
     */
    val semanticActions: MediaButton? = null,
    /**
     * Package name of the app that's posting the media.
     */
@@ -125,6 +130,32 @@ data class MediaData(
    }
}

/**
 * Contains [MediaAction] objects which represent specific buttons in the UI
 */
data class MediaButton(
    /**
     * Play/pause button
     */
    var playOrPause: MediaAction? = null,
    /**
     * Next button, or custom action
     */
    var nextOrCustom: MediaAction? = null,
    /**
     * Previous button, or custom action
     */
    var prevOrCustom: MediaAction? = null,
    /**
     * First custom action space
     */
    var startCustom: MediaAction? = null,
    /**
     * Last custom action space
     */
    var endCustom: MediaAction? = null
)

/** State of a media action. */
data class MediaAction(
    val icon: Icon?,
Loading