Add PlayerNotificationManager.Builder

PiperOrigin-RevId: 355356169
This commit is contained in:
bachinger 2021-02-03 10:45:52 +00:00 committed by Oliver Woodman
parent 584a0730c9
commit 63ae8f5417
2 changed files with 339 additions and 110 deletions

View File

@ -3,6 +3,17 @@
### dev-v2 (not yet released)
* New release notes go here!
* Core library:
* Log a warning when `SingleSampleMediaPeriod` transforms a load error
into end-of-stream.
* UI:
* Add builder for `PlayerNotificationManager`.
* Extractors:
* Fix Vorbis private codec data parsing in the Matroska extractor
([#8496](https://github.com/google/ExoPlayer/issues/8496)).
* Text:
* Add support for the SSA `primaryColour` style attribute
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
### 2.13.0 (not yet released - targeted for 2021-02-TBD)

View File

@ -23,6 +23,8 @@ import static com.google.android.exoplayer2.Player.EVENT_POSITION_DISCONTINUITY;
import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.app.Notification;
import android.app.NotificationChannel;
@ -50,7 +52,6 @@ import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
@ -144,10 +145,12 @@ import java.util.Map;
* <li><b>{@code exo_notification_stop}</b> - The stop icon.
* </ul>
*
* Unlike the drawables above, the large icon (i.e. the icon passed to {@link
* <p>Alternatively, the action icons can be set programatically by using the {@link Builder}.
*
* <p>Unlike the drawables above, the large icon (i.e. the icon passed to {@link
* NotificationCompat.Builder#setLargeIcon(Bitmap)} cannot be overridden in this way. Instead, the
* large icon is obtained from the {@link MediaDescriptionAdapter} injected when creating the
* PlayerNotificationManager.
* large icon is obtained from the {@link MediaDescriptionAdapter} passed to {@link
* Builder#Builder(Context, int, String, MediaDescriptionAdapter)}.
*/
public class PlayerNotificationManager {
@ -296,6 +299,249 @@ public class PlayerNotificationManager {
int notificationId, Notification notification, boolean ongoing) {}
}
/** A builder for {@link PlayerNotificationManager} instances. */
public static class Builder {
private final Context context;
private final int notificationId;
private final String channelId;
private final MediaDescriptionAdapter mediaDescriptionAdapter;
@Nullable private NotificationListener notificationListener;
@Nullable private CustomActionReceiver customActionReceiver;
private int channelNameResourceId;
private int channelDescriptionResourceId;
private int channelImportance;
private int smallIconResourceId;
private int rewindActionIconResourceId;
private int playActionIconResourceId;
private int pauseActionIconResourceId;
private int stopActionIconResourceId;
private int fastForwardActionIconResourceId;
private int previousActionIconResourceId;
private int nextActionIconResourceId;
/**
* Creates an instance.
*
* @param context The {@link Context}.
* @param notificationId The id of the notification to be posted. Must be greater than 0.
* @param channelId The id of the notification channel.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter} to be used.
*/
public Builder(
Context context,
int notificationId,
String channelId,
MediaDescriptionAdapter mediaDescriptionAdapter) {
checkArgument(notificationId > 0);
this.context = context;
this.notificationId = notificationId;
this.channelId = channelId;
this.mediaDescriptionAdapter = mediaDescriptionAdapter;
channelImportance = NotificationUtil.IMPORTANCE_LOW;
smallIconResourceId = R.drawable.exo_notification_small_icon;
playActionIconResourceId = R.drawable.exo_notification_play;
pauseActionIconResourceId = R.drawable.exo_notification_pause;
stopActionIconResourceId = R.drawable.exo_notification_stop;
rewindActionIconResourceId = R.drawable.exo_notification_rewind;
fastForwardActionIconResourceId = R.drawable.exo_notification_fastforward;
previousActionIconResourceId = R.drawable.exo_notification_previous;
nextActionIconResourceId = R.drawable.exo_notification_next;
}
/**
* The name of the channel. If set to a value other than {@code 0}, the channel is automatically
* created when {@link #build()} is called. If the application has already created the
* notification channel, then this method should not be called.
*
* <p>The default is {@code 0}.
*
* @return This builder.
*/
public Builder setChannelNameResourceId(int channelNameResourceId) {
this.channelNameResourceId = channelNameResourceId;
return this;
}
/**
* The description of the channel. Ignored if {@link #setChannelNameResourceId(int)} is not
* called with a value other than {@code 0}. If the application has already created the
* notification channel, then this method should not be called.
*
* <p>The default is {@code 0}.
*
* @return This builder.
*/
public Builder setChannelDescriptionResourceId(int channelDescriptionResourceId) {
this.channelDescriptionResourceId = channelDescriptionResourceId;
return this;
}
/**
* The importance of the channel. Ignored if {@link #setChannelNameResourceId(int)} is not
* called with a value other than {@code 0}. If the application has already created the
* notification channel, then this method should not be called.
*
* <p>The default is {@link NotificationUtil#IMPORTANCE_LOW}.
*
* @return This builder.
*/
public Builder setChannelImportance(@NotificationUtil.Importance int channelImportance) {
this.channelImportance = channelImportance;
return this;
}
/**
* The {@link NotificationListener} to be used.
*
* <p>The default is {@code null}.
*
* @return This builder.
*/
public Builder setNotificationListener(NotificationListener notificationListener) {
this.notificationListener = notificationListener;
return this;
}
/**
* The {@link CustomActionReceiver} to be used.
*
* <p>The default is {@code null}.
*
* @return This builder.
*/
public Builder setCustomActionReceiver(CustomActionReceiver customActionReceiver) {
this.customActionReceiver = customActionReceiver;
return this;
}
/**
* The resource id of the small icon of the notification shown in the status bar. See {@link
* NotificationCompat.Builder#setSmallIcon(int)}.
*
* <p>The default is {@link R.drawable#exo_notification_small_icon}.
*
* @return This builder.
*/
public Builder setSmallIconResourceId(int smallIconResourceId) {
this.smallIconResourceId = smallIconResourceId;
return this;
}
/**
* The resource id of the drawable to be used as the icon of action {@link #ACTION_PLAY}.
*
* <p>The default is {@link R.drawable#exo_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 {@link R.drawable#exo_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_STOP}.
*
* <p>The default is {@link R.drawable#exo_notification_stop}.
*
* @return This builder.
*/
public Builder setStopActionIconResourceId(int stopActionIconResourceId) {
this.stopActionIconResourceId = stopActionIconResourceId;
return this;
}
/**
* The resource id of the drawable to be used as the icon of action {@link #ACTION_REWIND}.
*
* <p>The default is {@link R.drawable#exo_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 {@link R.drawable#exo_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 {@link R.drawable#exo_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 {@link R.drawable#exo_notification_next}.
*
* @return This builder.
*/
public Builder setNextActionIconResourceId(int nextActionIconResourceId) {
this.nextActionIconResourceId = nextActionIconResourceId;
return this;
}
/** Builds the {@link PlayerNotificationManager}. */
public PlayerNotificationManager build() {
if (channelNameResourceId != 0) {
NotificationUtil.createNotificationChannel(
context,
channelId,
channelNameResourceId,
channelDescriptionResourceId,
channelImportance);
}
return new PlayerNotificationManager(
context,
channelId,
notificationId,
mediaDescriptionAdapter,
notificationListener,
customActionReceiver,
smallIconResourceId,
playActionIconResourceId,
pauseActionIconResourceId,
stopActionIconResourceId,
rewindActionIconResourceId,
fastForwardActionIconResourceId,
previousActionIconResourceId,
nextActionIconResourceId);
}
}
/** Receives a {@link Bitmap}. */
public final class BitmapCallback {
private final int notificationTag;
@ -417,10 +663,8 @@ public class PlayerNotificationManager {
@Priority private int priority;
private boolean useChronometer;
/**
* @deprecated Use {@link #createWithNotificationChannel(Context, String, int, int, int,
* MediaDescriptionAdapter)}.
*/
/** @deprecated Use the {@link Builder} instead. */
@SuppressWarnings("deprecation")
@Deprecated
public static PlayerNotificationManager createWithNotificationChannel(
Context context,
@ -437,27 +681,8 @@ public class PlayerNotificationManager {
mediaDescriptionAdapter);
}
/**
* Creates a notification manager and a low-priority notification channel with the specified
* {@code channelId} and {@code channelName}.
*
* <p>If the player notification manager is intended to be used within a foreground service,
* {@link #createWithNotificationChannel(Context, String, int, int, MediaDescriptionAdapter,
* NotificationListener)} should be used to which a {@link NotificationListener} can be passed.
* This way you'll receive the notification to put the service into the foreground by calling
* {@link android.app.Service#startForeground(int, Notification)}.
*
* @param context The {@link Context}.
* @param channelId The id of the notification channel.
* @param channelName A string resource identifier for the user visible name of the notification
* channel. The recommended maximum length is 40 characters. The string may be truncated if
* it's too long.
* @param channelDescription A string resource identifier for the user visible description of the
* notification channel, or 0 if no description is provided. The recommended maximum length is
* 300 characters. The value may be truncated if it is too long.
* @param notificationId The id of the notification.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
*/
/** @deprecated Use the {@link Builder} instead. */
@Deprecated
public static PlayerNotificationManager createWithNotificationChannel(
Context context,
String channelId,
@ -471,10 +696,7 @@ public class PlayerNotificationManager {
context, channelId, notificationId, mediaDescriptionAdapter);
}
/**
* @deprecated Use {@link #createWithNotificationChannel(Context, String, int, int, int,
* MediaDescriptionAdapter, NotificationListener)}.
*/
/** @deprecated Use the {@link Builder} instead. */
@Deprecated
public static PlayerNotificationManager createWithNotificationChannel(
Context context,
@ -493,21 +715,8 @@ public class PlayerNotificationManager {
notificationListener);
}
/**
* Creates a notification manager and a low-priority notification channel with the specified
* {@code channelId} and {@code channelName}. The {@link NotificationListener} passed as the last
* parameter will be notified when the notification is created and cancelled.
*
* @param context The {@link Context}.
* @param channelId The id of the notification channel.
* @param channelName A string resource identifier for the user visible name of the channel. The
* recommended maximum length is 40 characters. The string may be truncated if it's too long.
* @param channelDescription A string resource identifier for the user visible description of the
* channel, or 0 if no description is provided.
* @param notificationId The id of the notification.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
* @param notificationListener The {@link NotificationListener}.
*/
/** @deprecated Use the {@link Builder} instead. */
@Deprecated
public static PlayerNotificationManager createWithNotificationChannel(
Context context,
String channelId,
@ -522,19 +731,8 @@ public class PlayerNotificationManager {
context, channelId, notificationId, mediaDescriptionAdapter, notificationListener);
}
/**
* Creates a notification manager using the specified notification {@code channelId}. The caller
* is responsible for creating the notification channel.
*
* <p>When used within a service, consider using {@link #PlayerNotificationManager(Context,
* String, int, MediaDescriptionAdapter, NotificationListener)} to which a {@link
* NotificationListener} can be passed.
*
* @param context The {@link Context}.
* @param channelId The id of the notification channel.
* @param notificationId The id of the notification.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
*/
/** @deprecated Use the {@link Builder} instead. */
@Deprecated
public PlayerNotificationManager(
Context context,
String channelId,
@ -549,16 +747,8 @@ public class PlayerNotificationManager {
/* customActionReceiver */ null);
}
/**
* Creates a notification manager using the specified notification {@code channelId} and {@link
* NotificationListener}. The caller is responsible for creating the notification channel.
*
* @param context The {@link Context}.
* @param channelId The id of the notification channel.
* @param notificationId The id of the notification.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
* @param notificationListener The {@link NotificationListener}.
*/
/** @deprecated Use the {@link Builder} instead. */
@Deprecated
public PlayerNotificationManager(
Context context,
String channelId,
@ -574,20 +764,9 @@ public class PlayerNotificationManager {
/* customActionReceiver= */ null);
}
/**
* Creates a notification manager using the specified notification {@code channelId} and {@link
* CustomActionReceiver}. The caller is responsible for creating the notification channel.
*
* <p>When used within a service, consider using {@link #PlayerNotificationManager(Context,
* String, int, MediaDescriptionAdapter, NotificationListener, CustomActionReceiver)} to which a
* {@link NotificationListener} can be passed.
*
* @param context The {@link Context}.
* @param channelId The id of the notification channel.
* @param notificationId The id of the notification.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
* @param customActionReceiver The {@link CustomActionReceiver}.
*/
/** @deprecated Use the {@link Builder} instead. */
@SuppressWarnings("deprecation")
@Deprecated
public PlayerNotificationManager(
Context context,
String channelId,
@ -603,18 +782,8 @@ public class PlayerNotificationManager {
customActionReceiver);
}
/**
* Creates a notification manager using the specified notification {@code channelId}, {@link
* NotificationListener} and {@link CustomActionReceiver}. The caller is responsible for creating
* the notification channel.
*
* @param context The {@link Context}.
* @param channelId The id of the notification channel.
* @param notificationId The id of the notification.
* @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
* @param notificationListener The {@link NotificationListener}.
* @param customActionReceiver The {@link CustomActionReceiver}.
*/
/** @deprecated Use the {@link Builder} instead. */
@Deprecated
public PlayerNotificationManager(
Context context,
String channelId,
@ -622,6 +791,38 @@ public class PlayerNotificationManager {
MediaDescriptionAdapter mediaDescriptionAdapter,
@Nullable NotificationListener notificationListener,
@Nullable CustomActionReceiver customActionReceiver) {
this(
context,
channelId,
notificationId,
mediaDescriptionAdapter,
notificationListener,
customActionReceiver,
R.drawable.exo_notification_small_icon,
R.drawable.exo_notification_play,
R.drawable.exo_notification_pause,
R.drawable.exo_notification_stop,
R.drawable.exo_notification_rewind,
R.drawable.exo_notification_fastforward,
R.drawable.exo_notification_previous,
R.drawable.exo_notification_next);
}
private PlayerNotificationManager(
Context context,
String channelId,
int notificationId,
MediaDescriptionAdapter mediaDescriptionAdapter,
@Nullable NotificationListener notificationListener,
@Nullable CustomActionReceiver customActionReceiver,
int smallIconResourceId,
int playActionIconResourceId,
int pauseActionIconResourceId,
int stopActionIconResourceId,
int rewindActionIconResourceId,
int fastForwardActionIconResourceId,
int previousActionIconResourceId,
int nextActionIconResourceId) {
context = context.getApplicationContext();
this.context = context;
this.channelId = channelId;
@ -629,6 +830,7 @@ public class PlayerNotificationManager {
this.mediaDescriptionAdapter = mediaDescriptionAdapter;
this.notificationListener = notificationListener;
this.customActionReceiver = customActionReceiver;
this.smallIconResourceId = smallIconResourceId;
controlDispatcher = new DefaultControlDispatcher();
window = new Timeline.Window();
instanceId = instanceIdCounter++;
@ -648,14 +850,23 @@ public class PlayerNotificationManager {
colorized = true;
useChronometer = true;
color = Color.TRANSPARENT;
smallIconResourceId = R.drawable.exo_notification_small_icon;
defaults = 0;
priority = NotificationCompat.PRIORITY_LOW;
badgeIconType = NotificationCompat.BADGE_ICON_SMALL;
visibility = NotificationCompat.VISIBILITY_PUBLIC;
// initialize actions
playbackActions = createPlaybackActions(context, instanceId);
playbackActions =
createPlaybackActions(
context,
instanceId,
playActionIconResourceId,
pauseActionIconResourceId,
stopActionIconResourceId,
rewindActionIconResourceId,
fastForwardActionIconResourceId,
previousActionIconResourceId,
nextActionIconResourceId);
for (String action : playbackActions.keySet()) {
intentFilter.addAction(action);
}
@ -685,9 +896,8 @@ public class PlayerNotificationManager {
* player.getApplicationLooper() == Looper.getMainLooper()}).
*/
public final void setPlayer(@Nullable Player player) {
Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
Assertions.checkArgument(
player == null || player.getApplicationLooper() == Looper.getMainLooper());
checkState(Looper.myLooper() == Looper.getMainLooper());
checkArgument(player == null || player.getApplicationLooper() == Looper.getMainLooper());
if (this.player == player) {
return;
}
@ -1341,48 +1551,56 @@ public class PlayerNotificationManager {
}
private static Map<String, NotificationCompat.Action> createPlaybackActions(
Context context, int instanceId) {
Context context,
int instanceId,
int playActionIconResourceId,
int pauseActionIconResourceId,
int stopActionIconResourceId,
int rewindActionIconResourceId,
int fastForwardActionIconResourceId,
int previousActionIconResourceId,
int nextActionIconResourceId) {
Map<String, NotificationCompat.Action> actions = new HashMap<>();
actions.put(
ACTION_PLAY,
new NotificationCompat.Action(
R.drawable.exo_notification_play,
playActionIconResourceId,
context.getString(R.string.exo_controls_play_description),
createBroadcastIntent(ACTION_PLAY, context, instanceId)));
actions.put(
ACTION_PAUSE,
new NotificationCompat.Action(
R.drawable.exo_notification_pause,
pauseActionIconResourceId,
context.getString(R.string.exo_controls_pause_description),
createBroadcastIntent(ACTION_PAUSE, context, instanceId)));
actions.put(
ACTION_STOP,
new NotificationCompat.Action(
R.drawable.exo_notification_stop,
stopActionIconResourceId,
context.getString(R.string.exo_controls_stop_description),
createBroadcastIntent(ACTION_STOP, context, instanceId)));
actions.put(
ACTION_REWIND,
new NotificationCompat.Action(
R.drawable.exo_notification_rewind,
rewindActionIconResourceId,
context.getString(R.string.exo_controls_rewind_description),
createBroadcastIntent(ACTION_REWIND, context, instanceId)));
actions.put(
ACTION_FAST_FORWARD,
new NotificationCompat.Action(
R.drawable.exo_notification_fastforward,
fastForwardActionIconResourceId,
context.getString(R.string.exo_controls_fastforward_description),
createBroadcastIntent(ACTION_FAST_FORWARD, context, instanceId)));
actions.put(
ACTION_PREVIOUS,
new NotificationCompat.Action(
R.drawable.exo_notification_previous,
previousActionIconResourceId,
context.getString(R.string.exo_controls_previous_description),
createBroadcastIntent(ACTION_PREVIOUS, context, instanceId)));
actions.put(
ACTION_NEXT,
new NotificationCompat.Action(
R.drawable.exo_notification_next,
nextActionIconResourceId,
context.getString(R.string.exo_controls_next_description),
createBroadcastIntent(ACTION_NEXT, context, instanceId)));
return actions;