Utilize CommandButton for customizing Notification

PiperOrigin-RevId: 416421456
This commit is contained in:
jaewan 2021-12-15 00:23:54 +00:00 committed by tonihei
parent 531ff0e19d
commit df2e37acbe

View file

@ -15,6 +15,8 @@
*/ */
package androidx.media3.session; 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_BACK;
import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD; import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT; import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
@ -40,6 +42,7 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
@ -53,6 +56,8 @@ import androidx.core.app.NotificationManagerCompat;
import androidx.media.app.NotificationCompat.MediaStyle; import androidx.media.app.NotificationCompat.MediaStyle;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Player; 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.NotificationUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
@ -62,7 +67,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -180,12 +184,12 @@ public class PlayerNotificationManager {
* *
* <p>If multiple {@link PlayerNotificationManager} instances are in use at the same time, the * <p>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 * {@code instanceId} must be set as an intent extra with key {@link
* PlayerNotificationManager#EXTRA_INSTANCE_ID} to avoid sending the action to every custom * PlayerNotificationManager#INTENT_EXTRA_INSTANCE_ID} to avoid sending the action to every
* action receiver. It's also necessary to ensure something is different about the actions. This * custom action receiver. It's also necessary to ensure something is different about the
* may be any of the {@link Intent} attributes considered by {@link Intent#filterEquals}, or * actions. This may be any of the {@link Intent} attributes considered by {@link
* different request code integers when creating the {@link PendingIntent}s with {@link * Intent#filterEquals}, or different request code integers when creating the {@link
* PendingIntent#getBroadcast}. The easiest approach is to use the {@code instanceId} as the * PendingIntent}s with {@link PendingIntent#getBroadcast}. The easiest approach is to use the
* request code. * {@code instanceId} as the request code.
* *
* @param context The {@link Context}. * @param context The {@link Context}.
* @param instanceId The instance id of the {@link PlayerNotificationManager}. * @param instanceId The instance id of the {@link PlayerNotificationManager}.
@ -251,12 +255,6 @@ public class PlayerNotificationManager {
protected int channelDescriptionResourceId; protected int channelDescriptionResourceId;
protected int channelImportance; protected int channelImportance;
protected int smallIconResourceId; 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; @Nullable protected String groupKey;
/** /**
@ -288,12 +286,6 @@ public class PlayerNotificationManager {
channelImportance = NotificationUtil.IMPORTANCE_LOW; channelImportance = NotificationUtil.IMPORTANCE_LOW;
mediaDescriptionAdapter = new DefaultMediaDescriptionAdapter(/* pendingIntent= */ null); mediaDescriptionAdapter = new DefaultMediaDescriptionAdapter(/* pendingIntent= */ null);
smallIconResourceId = R.drawable.media3_notification_small_icon; 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; return this;
} }
/**
* The resource id of the drawable to be used as the icon of action {@link #ACTION_PLAY}.
*
* <p>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}.
*
* <p>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}.
*
* <p>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}.
*
* <p>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}.
*
* <p>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}.
*
* <p>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. * The key of the group the media notification should belong to.
* *
@ -491,12 +410,6 @@ public class PlayerNotificationManager {
notificationListener, notificationListener,
customActionReceiver, customActionReceiver,
smallIconResourceId, smallIconResourceId,
playActionIconResourceId,
pauseActionIconResourceId,
rewindActionIconResourceId,
fastForwardActionIconResourceId,
previousActionIconResourceId,
nextActionIconResourceId,
groupKey); groupKey);
} }
} }
@ -522,30 +435,34 @@ public class PlayerNotificationManager {
} }
} }
/** The action which starts playback. */ /** The action which is executed when a button in the notification is clicked. */
public static final String ACTION_PLAY = "androidx.media3.ui.notification.play"; private static final String INTENT_ACTION_COMMAND = "androidx.media3.session.command";
/** 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 the notification is dismissed. It cancels the notification * The action which is executed when the notification is dismissed. It cancels the notification
* and calls {@link NotificationListener#onNotificationCancelled(int, boolean)}. * 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. // Internal messages.
private static final int MSG_START_OR_UPDATE_NOTIFICATION = 0; private static final int MSG_START_OR_UPDATE_NOTIFICATION = 1;
private static final int MSG_UPDATE_NOTIFICATION_BITMAP = 1; private static final int MSG_UPDATE_NOTIFICATION_BITMAP = 2;
/** /**
* Visibility of notification on the lock screen. One of {@link * Visibility of notification on the lock screen. One of {@link
@ -591,13 +508,17 @@ public class PlayerNotificationManager {
private final IntentFilter intentFilter; private final IntentFilter intentFilter;
private final Player.Listener playerListener; private final Player.Listener playerListener;
private final NotificationBroadcastReceiver notificationBroadcastReceiver; private final NotificationBroadcastReceiver notificationBroadcastReceiver;
private final Map<String, NotificationCompat.Action> playbackActions;
private final Map<String, NotificationCompat.Action> customActions; private final Map<String, NotificationCompat.Action> customActions;
private final PendingIntent dismissPendingIntent; private final PendingIntent dismissPendingIntent;
private final int instanceId; 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 NotificationCompat.Builder builder;
@Nullable private List<NotificationCompat.Action> builderActions;
@Nullable private Player player; @Nullable private Player player;
private boolean isNotificationStarted; private boolean isNotificationStarted;
private int currentNotificationTag; private int currentNotificationTag;
@ -620,12 +541,6 @@ public class PlayerNotificationManager {
@Nullable NotificationListener notificationListener, @Nullable NotificationListener notificationListener,
@Nullable CustomActionReceiver customActionReceiver, @Nullable CustomActionReceiver customActionReceiver,
int smallIconResourceId, int smallIconResourceId,
int playActionIconResourceId,
int pauseActionIconResourceId,
int rewindActionIconResourceId,
int fastForwardActionIconResourceId,
int previousActionIconResourceId,
int nextActionIconResourceId,
@Nullable String groupKey) { @Nullable String groupKey) {
context = context.getApplicationContext(); context = context.getApplicationContext();
this.context = context; this.context = context;
@ -655,29 +570,51 @@ public class PlayerNotificationManager {
badgeIconType = NotificationCompat.BADGE_ICON_SMALL; badgeIconType = NotificationCompat.BADGE_ICON_SMALL;
visibility = NotificationCompat.VISIBILITY_PUBLIC; visibility = NotificationCompat.VISIBILITY_PUBLIC;
// initialize actions // initialize default buttons
playbackActions = playButton =
createPlaybackActions( new CommandButton.Builder()
context, .setDisplayName(context.getText(R.string.media3_controls_play_description))
instanceId, .setIconResId(R.drawable.media3_notification_play)
playActionIconResourceId, .setPlayerCommand(COMMAND_PLAY_PAUSE)
pauseActionIconResourceId, .build();
rewindActionIconResourceId, pauseButton =
fastForwardActionIconResourceId, new CommandButton.Builder()
previousActionIconResourceId, .setDisplayName(context.getText(R.string.media3_controls_pause_description))
nextActionIconResourceId); .setIconResId(R.drawable.media3_notification_pause)
for (String action : playbackActions.keySet()) { .setPlayerCommand(COMMAND_PLAY_PAUSE)
intentFilter.addAction(action); .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 = customActions =
customActionReceiver != null customActionReceiver != null
? customActionReceiver.createCustomActions(context, instanceId) ? customActionReceiver.createCustomActions(context, instanceId)
: Collections.emptyMap(); : Collections.emptyMap();
for (String action : customActions.keySet()) { dismissPendingIntent = createBroadcastIntent(context, INTENT_ACTION_DISMISS, instanceId);
intentFilter.addAction(action);
}
dismissPendingIntent = createBroadcastIntent(ACTION_DISMISS, context, instanceId);
intentFilter.addAction(ACTION_DISMISS);
} }
/** /**
@ -844,7 +781,7 @@ public class PlayerNotificationManager {
* *
* <ul> * <ul>
* <li>The media is {@link Player#isPlaying() actively playing}. * <li>The media is {@link Player#isPlaying() actively playing}.
* <li>The media is not {@link Player#isCurrentMediaItemDynamic() dynamically changing its * <li>The media is not {@link Player#isCurrentWindowDynamic() dynamically changing its
* duration} (like for example a live stream). * duration} (like for example a live stream).
* <li>The media is not {@link Player#isPlayingAd() interrupted by an ad}. * <li>The media is not {@link Player#isPlayingAd() interrupted by an ad}.
* <li>The media is played at {@link Player#getPlaybackParameters() regular speed}. * <li>The media is played at {@link Player#getPlaybackParameters() regular speed}.
@ -947,37 +884,29 @@ public class PlayerNotificationManager {
boolean ongoing, boolean ongoing,
@Nullable Bitmap largeIcon) { @Nullable Bitmap largeIcon) {
if (player.getPlaybackState() == Player.STATE_IDLE && player.getCurrentTimeline().isEmpty()) { if (player.getPlaybackState() == Player.STATE_IDLE && player.getCurrentTimeline().isEmpty()) {
builderActions = null;
return null; return null;
} }
List<String> actionNames = getActions(player); if (builder == null) {
List<NotificationCompat.Action> actions = new ArrayList<>(actionNames.size());
for (int i = 0; i < actionNames.size(); i++) {
String actionName = actionNames.get(i);
@Nullable
NotificationCompat.Action action =
playbackActions.containsKey(actionName)
? playbackActions.get(actionName)
: customActions.get(actionName);
if (action != null) {
actions.add(action);
}
}
if (builder == null || !actions.equals(builderActions)) {
builder = new NotificationCompat.Builder(context, channelId); builder = new NotificationCompat.Builder(context, channelId);
builderActions = actions; }
for (int i = 0; i < actions.size(); i++) { List<CommandButton> actionButtons = getActionButtons(player);
builder.addAction(actions.get(i)); for (int i = 0; i < actionButtons.size(); i++) {
} CommandButton button = actionButtons.get(i);
NotificationCompat.Action action =
new NotificationCompat.Action(
button.iconResId,
button.displayName,
createBroadcastIntent(context, button, instanceId));
builder.addAction(action);
} }
MediaStyle mediaStyle = new MediaStyle(); MediaStyle mediaStyle = new MediaStyle();
if (mediaSessionToken != null) { if (mediaSessionToken != null) {
mediaStyle.setMediaSession(mediaSessionToken); mediaStyle.setMediaSession(mediaSessionToken);
} }
mediaStyle.setShowActionsInCompactView(getActionIndicesForCompactView(actionNames, player)); mediaStyle.setShowActionsInCompactView(
getActionButtonIndicesForCompactView(actionButtons, player));
// Configure dismiss action prior to API 21 ('x' button). // Configure dismiss action prior to API 21 ('x' button).
mediaStyle.setShowCancelButton(!ongoing); mediaStyle.setShowCancelButton(!ongoing);
mediaStyle.setCancelButtonIntent(dismissPendingIntent); mediaStyle.setCancelButtonIntent(dismissPendingIntent);
@ -1002,7 +931,7 @@ public class PlayerNotificationManager {
&& useChronometer && useChronometer
&& player.isPlaying() && player.isPlaying()
&& !player.isPlayingAd() && !player.isPlayingAd()
&& !player.isCurrentMediaItemDynamic() && !player.isCurrentWindowDynamic()
&& player.getPlaybackParameters().speed == 1f) { && player.getPlaybackParameters().speed == 1f) {
builder builder
.setWhen(System.currentTimeMillis() - player.getContentPosition()) .setWhen(System.currentTimeMillis() - player.getContentPosition())
@ -1040,42 +969,39 @@ public class PlayerNotificationManager {
* omitted: * omitted:
* *
* <pre> * <pre>
* +-----------------------------------------------------------------+ * +------------------------------------------------+
* | prev | &lt;&lt; | play/pause | &gt;&gt; | next | custom actions | * | prev | &lt;&lt; | play/pause | &gt;&gt; | next |
* +-----------------------------------------------------------------+ * +------------------------------------------------+
* </pre> * </pre>
* *
* <p>This method can be safely overridden. However, the names must be of the playback actions * <p>This method can be safely overridden.
* {@link #ACTION_PAUSE}, {@link #ACTION_PLAY}, {@link #ACTION_FAST_FORWARD}, {@link
* #ACTION_REWIND}, {@link #ACTION_NEXT} or {@link #ACTION_PREVIOUS}, or a key contained in the
* map returned by {@link CustomActionReceiver#createCustomActions(Context, int)}. Otherwise the
* action name is ignored.
*/ */
protected List<String> getActions(Player player) { // Disclaimer: Custom action support is temporarily removed, but will be added back in next CL.
protected List<CommandButton> getActionButtons(Player player) {
boolean enablePrevious = player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS); boolean enablePrevious = player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS);
boolean enableRewind = player.isCommandAvailable(COMMAND_SEEK_BACK); boolean enableRewind = player.isCommandAvailable(COMMAND_SEEK_BACK);
boolean enableFastForward = player.isCommandAvailable(COMMAND_SEEK_FORWARD); boolean enableFastForward = player.isCommandAvailable(COMMAND_SEEK_FORWARD);
boolean enableNext = player.isCommandAvailable(COMMAND_SEEK_TO_NEXT); boolean enableNext = player.isCommandAvailable(COMMAND_SEEK_TO_NEXT);
List<String> stringActions = new ArrayList<>(); List<CommandButton> buttons = new ArrayList<>();
if (enablePrevious) { if (enablePrevious) {
stringActions.add(ACTION_PREVIOUS); buttons.add(seekToPreviousButton);
} }
if (enableRewind) { if (enableRewind) {
stringActions.add(ACTION_REWIND); buttons.add(seekBackButton);
} }
if (shouldShowPauseButton(player)) { if (shouldShowPauseButton(player)) {
stringActions.add(ACTION_PAUSE); buttons.add(pauseButton);
} else { } else {
stringActions.add(ACTION_PLAY); buttons.add(playButton);
} }
if (enableFastForward) { if (enableFastForward) {
stringActions.add(ACTION_FAST_FORWARD); buttons.add(seekForwardButton);
} }
if (enableNext) { if (enableNext) {
stringActions.add(ACTION_NEXT); buttons.add(seekToNextButton);
} }
return stringActions; return buttons;
} }
/** /**
@ -1084,29 +1010,41 @@ public class PlayerNotificationManager {
* <p>This method can be overridden. The indices must refer to the list of actions passed as the * <p>This method can be overridden. The indices must refer to the list of actions passed as the
* first parameter. * first parameter.
* *
* @param actionNames The names of the actions included in the notification. * @param actionButtons The buttons of the actions included in the notification.
* @param player The player for which a notification is being built. * @param player The player for which a notification is being built.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
protected int[] getActionIndicesForCompactView(List<String> actionNames, Player player) { protected int[] getActionButtonIndicesForCompactView(
int pauseActionIndex = actionNames.indexOf(ACTION_PAUSE); List<CommandButton> actionButtons, Player player) {
int playActionIndex = actionNames.indexOf(ACTION_PLAY); int previousIndex = C.INDEX_UNSET;
int leftSideActionIndex = actionNames.indexOf(ACTION_PREVIOUS); int nextIndex = C.INDEX_UNSET;
int rightSideActionIndex = actionNames.indexOf(ACTION_NEXT); int playPauseIndex = C.INDEX_UNSET;
for (int i = 0; i < actionButtons.size(); i++) {
CommandButton button = actionButtons.get(i);
switch (button.playerCommand) {
case COMMAND_PLAY_PAUSE:
playPauseIndex = i;
break;
case COMMAND_SEEK_TO_PREVIOUS:
previousIndex = i;
break;
case COMMAND_SEEK_TO_NEXT:
nextIndex = i;
break;
default:
// Do nothing
}
}
int[] actionIndices = new int[3]; int[] actionIndices = new int[3];
int actionCounter = 0; int actionCounter = 0;
if (leftSideActionIndex != -1) { if (previousIndex != C.INDEX_UNSET) {
actionIndices[actionCounter++] = leftSideActionIndex; actionIndices[actionCounter++] = previousIndex;
} }
boolean shouldShowPauseButton = shouldShowPauseButton(player); if (playPauseIndex != C.INDEX_UNSET) {
if (pauseActionIndex != -1 && shouldShowPauseButton) { actionIndices[actionCounter++] = playPauseIndex;
actionIndices[actionCounter++] = pauseActionIndex;
} else if (playActionIndex != -1 && !shouldShowPauseButton) {
actionIndices[actionCounter++] = playActionIndex;
} }
if (rightSideActionIndex != -1) { if (nextIndex != C.INDEX_UNSET) {
actionIndices[actionCounter++] = rightSideActionIndex; actionIndices[actionCounter++] = nextIndex;
} }
return Arrays.copyOf(actionIndices, actionCounter); return Arrays.copyOf(actionIndices, actionCounter);
} }
@ -1155,68 +1093,30 @@ public class PlayerNotificationManager {
return true; return true;
} }
private static Map<String, NotificationCompat.Action> createPlaybackActions( private static PendingIntent createBroadcastIntent(
Context context, Context context, CommandButton button, int instanceId) {
int instanceId, Intent intent = new Intent(INTENT_ACTION_COMMAND).setPackage(context.getPackageName());
int playActionIconResourceId, intent.putExtra(INTENT_EXTRA_INSTANCE_ID, instanceId);
int pauseActionIconResourceId, intent.putExtra(INTENT_EXTRA_PLAYER_COMMAND, button.playerCommand);
int rewindActionIconResourceId, intent.putExtra(
int fastForwardActionIconResourceId, INTENT_EXTRA_SESSION_COMMAND, BundleableUtil.toNullableBundle(button.sessionCommand));
int previousActionIconResourceId, // Make intent distinguishable by Intent#filterEquals() due to the PendingIntent requirement.
int nextActionIconResourceId) { Uri intentUri =
Map<String, NotificationCompat.Action> actions = new HashMap<>(); new Uri.Builder()
actions.put( .scheme(INTENT_SCHEME)
ACTION_PLAY, .appendPath(Integer.toString(instanceId))
new NotificationCompat.Action( .appendPath(Integer.toString(button.playerCommand))
playActionIconResourceId, .appendPath(button.sessionCommand == null ? "null" : button.sessionCommand.customAction)
context.getString(R.string.media3_controls_play_description), .build();
createBroadcastIntent(ACTION_PLAY, context, instanceId))); intent.setData(intentUri);
actions.put( return PendingIntent.getBroadcast(context, instanceId, intent, PENDING_INTENT_FLAGS);
ACTION_PAUSE,
new NotificationCompat.Action(
pauseActionIconResourceId,
context.getString(R.string.media3_controls_pause_description),
createBroadcastIntent(ACTION_PAUSE, context, instanceId)));
actions.put(
ACTION_REWIND,
new NotificationCompat.Action(
rewindActionIconResourceId,
context.getString(R.string.media3_controls_seek_back_description),
createBroadcastIntent(ACTION_REWIND, context, instanceId)));
actions.put(
ACTION_FAST_FORWARD,
new NotificationCompat.Action(
fastForwardActionIconResourceId,
context.getString(R.string.media3_controls_seek_forward_description),
createBroadcastIntent(ACTION_FAST_FORWARD, context, instanceId)));
actions.put(
ACTION_PREVIOUS,
new NotificationCompat.Action(
previousActionIconResourceId,
context.getString(R.string.media3_controls_seek_to_previous_description),
createBroadcastIntent(ACTION_PREVIOUS, context, instanceId)));
actions.put(
ACTION_NEXT,
new NotificationCompat.Action(
nextActionIconResourceId,
context.getString(R.string.media3_controls_seek_to_next_description),
createBroadcastIntent(ACTION_NEXT, context, instanceId)));
return actions;
} }
private static PendingIntent createBroadcastIntent( private static PendingIntent createBroadcastIntent(
String action, Context context, int instanceId) { Context context, String action, int instanceId) {
Intent intent = new Intent(action).setPackage(context.getPackageName()); Intent intent = new Intent(action).setPackage(context.getPackageName());
intent.putExtra(EXTRA_INSTANCE_ID, instanceId); intent.putExtra(INTENT_EXTRA_INSTANCE_ID, instanceId);
return PendingIntent.getBroadcast(context, instanceId, intent, PENDING_INTENT_FLAGS);
int pendingFlags;
if (Util.SDK_INT >= 23) {
pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
} else {
pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT;
}
return PendingIntent.getBroadcast(context, instanceId, intent, pendingFlags);
} }
@SuppressWarnings("nullness:argument") @SuppressWarnings("nullness:argument")
@ -1251,28 +1151,42 @@ public class PlayerNotificationManager {
Player player = PlayerNotificationManager.this.player; Player player = PlayerNotificationManager.this.player;
if (player == null if (player == null
|| !isNotificationStarted || !isNotificationStarted
|| intent.getIntExtra(EXTRA_INSTANCE_ID, instanceId) != instanceId) { || intent.getIntExtra(INTENT_EXTRA_INSTANCE_ID, instanceId) != instanceId) {
return; return;
} }
String action = intent.getAction(); String action = intent.getAction();
if (ACTION_PLAY.equals(action)) { if (INTENT_ACTION_COMMAND.equals(action)) {
if (player.getPlaybackState() == Player.STATE_IDLE) { @Player.Command
player.prepare(); int playerCommand = intent.getIntExtra(INTENT_EXTRA_PLAYER_COMMAND, COMMAND_INVALID);
} else if (player.getPlaybackState() == Player.STATE_ENDED) { switch (playerCommand) {
player.seekToDefaultPosition(player.getCurrentMediaItemIndex()); case COMMAND_PLAY_PAUSE:
if (!player.getPlayWhenReady()) {
if (player.getPlaybackState() == Player.STATE_IDLE) {
player.prepare();
} else if (player.getPlaybackState() == Player.STATE_ENDED) {
player.seekToDefaultPosition(player.getCurrentWindowIndex());
}
} else {
player.pause();
}
break;
case COMMAND_SEEK_TO_PREVIOUS:
player.seekToPrevious();
break;
case COMMAND_SEEK_BACK:
player.seekBack();
break;
case COMMAND_SEEK_FORWARD:
player.seekForward();
break;
case COMMAND_SEEK_TO_NEXT:
player.seekToNext();
break;
default:
Log.w(TAG, "Unsupported player command, playerCommand=" + playerCommand);
break;
} }
player.play(); } else if (INTENT_ACTION_DISMISS.equals(action)) {
} else if (ACTION_PAUSE.equals(action)) {
player.pause();
} else if (ACTION_PREVIOUS.equals(action)) {
player.seekToPrevious();
} else if (ACTION_REWIND.equals(action)) {
player.seekBack();
} else if (ACTION_FAST_FORWARD.equals(action)) {
player.seekForward();
} else if (ACTION_NEXT.equals(action)) {
player.seekToNext();
} else if (ACTION_DISMISS.equals(action)) {
stopNotification(/* dismissedByUser= */ true); stopNotification(/* dismissedByUser= */ true);
} else if (action != null } else if (action != null
&& customActionReceiver != null && customActionReceiver != null