diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java b/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java index f3ed0599b1..534c076ebf 100644 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java +++ b/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java @@ -15,6 +15,8 @@ */ package androidx.media3.session; +import static androidx.media3.common.Player.COMMAND_INVALID; +import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE; import static androidx.media3.common.Player.COMMAND_SEEK_BACK; import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD; import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT; @@ -40,6 +42,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.Color; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -53,6 +56,8 @@ import androidx.core.app.NotificationManagerCompat; import androidx.media.app.NotificationCompat.MediaStyle; import androidx.media3.common.C; import androidx.media3.common.Player; +import androidx.media3.common.util.BundleableUtil; +import androidx.media3.common.util.Log; import androidx.media3.common.util.NotificationUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; @@ -62,7 +67,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -180,12 +184,12 @@ public class PlayerNotificationManager { * *

If multiple {@link PlayerNotificationManager} instances are in use at the same time, the * {@code instanceId} must be set as an intent extra with key {@link - * PlayerNotificationManager#EXTRA_INSTANCE_ID} to avoid sending the action to every custom - * action receiver. It's also necessary to ensure something is different about the actions. This - * may be any of the {@link Intent} attributes considered by {@link Intent#filterEquals}, or - * different request code integers when creating the {@link PendingIntent}s with {@link - * PendingIntent#getBroadcast}. The easiest approach is to use the {@code instanceId} as the - * request code. + * PlayerNotificationManager#INTENT_EXTRA_INSTANCE_ID} to avoid sending the action to every + * custom action receiver. It's also necessary to ensure something is different about the + * actions. This may be any of the {@link Intent} attributes considered by {@link + * Intent#filterEquals}, or different request code integers when creating the {@link + * PendingIntent}s with {@link PendingIntent#getBroadcast}. The easiest approach is to use the + * {@code instanceId} as the request code. * * @param context The {@link Context}. * @param instanceId The instance id of the {@link PlayerNotificationManager}. @@ -251,12 +255,6 @@ public class PlayerNotificationManager { protected int channelDescriptionResourceId; protected int channelImportance; protected int smallIconResourceId; - protected int rewindActionIconResourceId; - protected int playActionIconResourceId; - protected int pauseActionIconResourceId; - protected int fastForwardActionIconResourceId; - protected int previousActionIconResourceId; - protected int nextActionIconResourceId; @Nullable protected String groupKey; /** @@ -288,12 +286,6 @@ public class PlayerNotificationManager { channelImportance = NotificationUtil.IMPORTANCE_LOW; mediaDescriptionAdapter = new DefaultMediaDescriptionAdapter(/* pendingIntent= */ null); smallIconResourceId = R.drawable.media3_notification_small_icon; - playActionIconResourceId = R.drawable.media3_notification_play; - pauseActionIconResourceId = R.drawable.media3_notification_pause; - rewindActionIconResourceId = R.drawable.media3_notification_seek_back; - fastForwardActionIconResourceId = R.drawable.media3_notification_seek_forward; - previousActionIconResourceId = R.drawable.media3_notification_seek_to_previous; - nextActionIconResourceId = R.drawable.media3_notification_seek_to_next; } /** @@ -375,79 +367,6 @@ public class PlayerNotificationManager { return this; } - /** - * The resource id of the drawable to be used as the icon of action {@link #ACTION_PLAY}. - * - *

The default is {@code R.drawable#media3_notification_play}. - * - * @return This builder. - */ - public Builder setPlayActionIconResourceId(int playActionIconResourceId) { - this.playActionIconResourceId = playActionIconResourceId; - return this; - } - - /** - * The resource id of the drawable to be used as the icon of action {@link #ACTION_PAUSE}. - * - *

The default is {@code R.drawable#media3_notification_pause}. - * - * @return This builder. - */ - public Builder setPauseActionIconResourceId(int pauseActionIconResourceId) { - this.pauseActionIconResourceId = pauseActionIconResourceId; - return this; - } - - /** - * The resource id of the drawable to be used as the icon of action {@link #ACTION_REWIND}. - * - *

The default is {@code R.drawable#media3_notification_rewind}. - * - * @return This builder. - */ - public Builder setRewindActionIconResourceId(int rewindActionIconResourceId) { - this.rewindActionIconResourceId = rewindActionIconResourceId; - return this; - } - - /** - * The resource id of the drawable to be used as the icon of action {@link - * #ACTION_FAST_FORWARD}. - * - *

The default is {@code R.drawable#media3_notification_fastforward}. - * - * @return This builder. - */ - public Builder setFastForwardActionIconResourceId(int fastForwardActionIconResourceId) { - this.fastForwardActionIconResourceId = fastForwardActionIconResourceId; - return this; - } - - /** - * The resource id of the drawable to be used as the icon of action {@link #ACTION_PREVIOUS}. - * - *

The default is {@code R.drawable#media3_notification_previous}. - * - * @return This builder. - */ - public Builder setPreviousActionIconResourceId(int previousActionIconResourceId) { - this.previousActionIconResourceId = previousActionIconResourceId; - return this; - } - - /** - * The resource id of the drawable to be used as the icon of action {@link #ACTION_NEXT}. - * - *

The default is {@code R.drawable#media3_notification_next}. - * - * @return This builder. - */ - public Builder setNextActionIconResourceId(int nextActionIconResourceId) { - this.nextActionIconResourceId = nextActionIconResourceId; - return this; - } - /** * The key of the group the media notification should belong to. * @@ -491,12 +410,6 @@ public class PlayerNotificationManager { notificationListener, customActionReceiver, smallIconResourceId, - playActionIconResourceId, - pauseActionIconResourceId, - rewindActionIconResourceId, - fastForwardActionIconResourceId, - previousActionIconResourceId, - nextActionIconResourceId, groupKey); } } @@ -522,30 +435,34 @@ public class PlayerNotificationManager { } } - /** The action which starts playback. */ - public static final String ACTION_PLAY = "androidx.media3.ui.notification.play"; - /** The action which pauses playback. */ - public static final String ACTION_PAUSE = "androidx.media3.ui.notification.pause"; - /** The action which skips to the previous media item. */ - public static final String ACTION_PREVIOUS = "androidx.media3.ui.notification.prev"; - /** The action which skips to the next media item. */ - public static final String ACTION_NEXT = "androidx.media3.ui.notification.next"; - /** The action which fast forwards. */ - public static final String ACTION_FAST_FORWARD = "androidx.media3.ui.notification.ffwd"; - /** The action which rewinds. */ - public static final String ACTION_REWIND = "androidx.media3.ui.notification.rewind"; - /** The extra key of the instance id of the player notification manager. */ - public static final String EXTRA_INSTANCE_ID = "INSTANCE_ID"; + /** The action which is executed when a button in the notification is clicked. */ + private static final String INTENT_ACTION_COMMAND = "androidx.media3.session.command"; + /** * The action which is executed when the notification is dismissed. It cancels the notification * and calls {@link NotificationListener#onNotificationCancelled(int, boolean)}. */ - private static final String ACTION_DISMISS = "androidx.media3.ui.notification.dismiss"; + private static final String INTENT_ACTION_DISMISS = + "androidx.media3.session.notification.dismiss"; + + private static final String INTENT_EXTRA_PLAYER_COMMAND = + "androidx.media3.session.EXTRA_PLAYER_COMMAND"; + private static final String INTENT_EXTRA_SESSION_COMMAND = + "androidx.media3.session.EXTRA_SESSION_COMMAND"; + private static final String INTENT_EXTRA_INSTANCE_ID = + "androidx.media3.session.notificaiton.EXTRA_INSTANCE_ID"; + private static final String INTENT_SCHEME = "media3"; + + private static final int PENDING_INTENT_FLAGS = + (Util.SDK_INT >= 23) + ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + : PendingIntent.FLAG_UPDATE_CURRENT; + private static final String TAG = "NotificationManager"; // Internal messages. - private static final int MSG_START_OR_UPDATE_NOTIFICATION = 0; - private static final int MSG_UPDATE_NOTIFICATION_BITMAP = 1; + private static final int MSG_START_OR_UPDATE_NOTIFICATION = 1; + private static final int MSG_UPDATE_NOTIFICATION_BITMAP = 2; /** * Visibility of notification on the lock screen. One of {@link @@ -591,13 +508,17 @@ public class PlayerNotificationManager { private final IntentFilter intentFilter; private final Player.Listener playerListener; private final NotificationBroadcastReceiver notificationBroadcastReceiver; - private final Map playbackActions; private final Map customActions; private final PendingIntent dismissPendingIntent; private final int instanceId; + private final CommandButton playButton; + private final CommandButton pauseButton; + private final CommandButton seekToPreviousButton; + private final CommandButton seekToNextButton; + private final CommandButton seekBackButton; + private final CommandButton seekForwardButton; @Nullable private NotificationCompat.Builder builder; - @Nullable private List builderActions; @Nullable private Player player; private boolean isNotificationStarted; private int currentNotificationTag; @@ -620,12 +541,6 @@ public class PlayerNotificationManager { @Nullable NotificationListener notificationListener, @Nullable CustomActionReceiver customActionReceiver, int smallIconResourceId, - int playActionIconResourceId, - int pauseActionIconResourceId, - int rewindActionIconResourceId, - int fastForwardActionIconResourceId, - int previousActionIconResourceId, - int nextActionIconResourceId, @Nullable String groupKey) { context = context.getApplicationContext(); this.context = context; @@ -655,29 +570,51 @@ public class PlayerNotificationManager { badgeIconType = NotificationCompat.BADGE_ICON_SMALL; visibility = NotificationCompat.VISIBILITY_PUBLIC; - // initialize actions - playbackActions = - createPlaybackActions( - context, - instanceId, - playActionIconResourceId, - pauseActionIconResourceId, - rewindActionIconResourceId, - fastForwardActionIconResourceId, - previousActionIconResourceId, - nextActionIconResourceId); - for (String action : playbackActions.keySet()) { - intentFilter.addAction(action); - } + // initialize default buttons + playButton = + new CommandButton.Builder() + .setDisplayName(context.getText(R.string.media3_controls_play_description)) + .setIconResId(R.drawable.media3_notification_play) + .setPlayerCommand(COMMAND_PLAY_PAUSE) + .build(); + pauseButton = + new CommandButton.Builder() + .setDisplayName(context.getText(R.string.media3_controls_pause_description)) + .setIconResId(R.drawable.media3_notification_pause) + .setPlayerCommand(COMMAND_PLAY_PAUSE) + .build(); + seekToPreviousButton = + new CommandButton.Builder() + .setDisplayName(context.getText(R.string.media3_controls_seek_to_previous_description)) + .setIconResId(R.drawable.media3_notification_seek_to_previous) + .setPlayerCommand(COMMAND_SEEK_TO_PREVIOUS) + .build(); + seekToNextButton = + new CommandButton.Builder() + .setDisplayName(context.getText(R.string.media3_controls_seek_to_next_description)) + .setIconResId(R.drawable.media3_notification_seek_to_next) + .setPlayerCommand(COMMAND_SEEK_TO_NEXT) + .build(); + seekBackButton = + new CommandButton.Builder() + .setDisplayName(context.getText(R.string.media3_controls_seek_back_description)) + .setIconResId(R.drawable.media3_notification_seek_back) + .setPlayerCommand(COMMAND_SEEK_BACK) + .build(); + seekForwardButton = + new CommandButton.Builder() + .setDisplayName(context.getText(R.string.media3_controls_seek_forward_description)) + .setIconResId(R.drawable.media3_notification_seek_forward) + .setPlayerCommand(COMMAND_SEEK_FORWARD) + .build(); + intentFilter.addAction(INTENT_ACTION_COMMAND); + intentFilter.addAction(INTENT_ACTION_DISMISS); + intentFilter.addDataScheme(INTENT_SCHEME); customActions = customActionReceiver != null ? customActionReceiver.createCustomActions(context, instanceId) : Collections.emptyMap(); - for (String action : customActions.keySet()) { - intentFilter.addAction(action); - } - dismissPendingIntent = createBroadcastIntent(ACTION_DISMISS, context, instanceId); - intentFilter.addAction(ACTION_DISMISS); + dismissPendingIntent = createBroadcastIntent(context, INTENT_ACTION_DISMISS, instanceId); } /** @@ -844,7 +781,7 @@ public class PlayerNotificationManager { * *