Prevent NPE in PlayerNotificationManager.

The app can set the player to null while messages from the player are still
in flight. This may cause NPEs.

Issue:#4238

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=196504077
This commit is contained in:
tonihei 2018-05-14 07:43:36 -07:00 committed by Andrew Lewis
parent 1af9334188
commit b3c3717007
2 changed files with 103 additions and 97 deletions

View file

@ -36,6 +36,7 @@ dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-media-compat:' + supportLibraryVersion implementation 'com.android.support:support-media-compat:' + supportLibraryVersion
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
} }
ext { ext {

View file

@ -50,6 +50,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* A notification manager to start, update and cancel a media style notification reflecting the * A notification manager to start, update and cancel a media style notification reflecting the
@ -205,7 +206,9 @@ public class PlayerNotificationManager {
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
if (notificationTag == currentNotificationTag && isNotificationStarted) { if (player != null
&& notificationTag == currentNotificationTag
&& isNotificationStarted) {
updateNotification(bitmap); updateNotification(bitmap);
} }
} }
@ -260,7 +263,7 @@ public class PlayerNotificationManager {
private final String channelId; private final String channelId;
private final int notificationId; private final int notificationId;
private final MediaDescriptionAdapter mediaDescriptionAdapter; private final MediaDescriptionAdapter mediaDescriptionAdapter;
private final CustomActionReceiver customActionReceiver; private final @Nullable CustomActionReceiver customActionReceiver;
private final Handler mainHandler; private final Handler mainHandler;
private final NotificationManagerCompat notificationManager; private final NotificationManagerCompat notificationManager;
private final IntentFilter intentFilter; private final IntentFilter intentFilter;
@ -269,12 +272,12 @@ public class PlayerNotificationManager {
private final Map<String, NotificationCompat.Action> playbackActions; private final Map<String, NotificationCompat.Action> playbackActions;
private final Map<String, NotificationCompat.Action> customActions; private final Map<String, NotificationCompat.Action> customActions;
private Player player; private @Nullable Player player;
private ControlDispatcher controlDispatcher; private ControlDispatcher controlDispatcher;
private boolean isNotificationStarted; private boolean isNotificationStarted;
private int currentNotificationTag; private int currentNotificationTag;
private NotificationListener notificationListener; private @Nullable NotificationListener notificationListener;
private MediaSessionCompat.Token mediaSessionToken; private @Nullable MediaSessionCompat.Token mediaSessionToken;
private boolean useNavigationActions; private boolean useNavigationActions;
private boolean usePlayPauseActions; private boolean usePlayPauseActions;
private @Nullable String stopAction; private @Nullable String stopAction;
@ -365,6 +368,20 @@ public class PlayerNotificationManager {
playerListener = new PlayerListener(); playerListener = new PlayerListener();
notificationBroadcastReceiver = new NotificationBroadcastReceiver(); notificationBroadcastReceiver = new NotificationBroadcastReceiver();
intentFilter = new IntentFilter(); intentFilter = new IntentFilter();
useNavigationActions = true;
usePlayPauseActions = true;
ongoing = true;
colorized = true;
useChronometer = true;
color = Color.TRANSPARENT;
smallIconResourceId = R.drawable.exo_notification_small_icon;
defaults = 0;
priority = NotificationCompat.PRIORITY_LOW;
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
rewindMs = DEFAULT_REWIND_MS;
stopAction = ACTION_STOP;
badgeIconType = NotificationCompat.BADGE_ICON_SMALL;
visibility = NotificationCompat.VISIBILITY_PUBLIC;
// initialize actions // initialize actions
playbackActions = createPlaybackActions(context); playbackActions = createPlaybackActions(context);
@ -378,22 +395,7 @@ public class PlayerNotificationManager {
for (String action : customActions.keySet()) { for (String action : customActions.keySet()) {
intentFilter.addAction(action); intentFilter.addAction(action);
} }
stopPendingIntent = Assertions.checkNotNull(playbackActions.get(ACTION_STOP)).actionIntent;
setStopAction(ACTION_STOP);
useNavigationActions = true;
usePlayPauseActions = true;
ongoing = true;
colorized = true;
useChronometer = true;
color = Color.TRANSPARENT;
smallIconResourceId = R.drawable.exo_notification_small_icon;
defaults = 0;
priority = NotificationCompat.PRIORITY_LOW;
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
rewindMs = DEFAULT_REWIND_MS;
setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL);
setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
} }
/** /**
@ -512,10 +514,9 @@ public class PlayerNotificationManager {
} }
this.stopAction = stopAction; this.stopAction = stopAction;
if (ACTION_STOP.equals(stopAction)) { if (ACTION_STOP.equals(stopAction)) {
stopPendingIntent = playbackActions.get(ACTION_STOP).actionIntent; stopPendingIntent = Assertions.checkNotNull(playbackActions.get(ACTION_STOP)).actionIntent;
} else if (stopAction != null) { } else if (stopAction != null) {
Assertions.checkArgument(customActions.containsKey(stopAction)); stopPendingIntent = Assertions.checkNotNull(customActions.get(stopAction)).actionIntent;
stopPendingIntent = customActions.get(stopAction).actionIntent;
} else { } else {
stopPendingIntent = null; stopPendingIntent = null;
} }
@ -698,13 +699,15 @@ public class PlayerNotificationManager {
maybeUpdateNotification(); maybeUpdateNotification();
} }
private Notification updateNotification(Bitmap bitmap) { @RequiresNonNull("player")
private Notification updateNotification(@Nullable Bitmap bitmap) {
Notification notification = createNotification(player, bitmap); Notification notification = createNotification(player, bitmap);
notificationManager.notify(notificationId, notification); notificationManager.notify(notificationId, notification);
return notification; return notification;
} }
private void startOrUpdateNotification() { private void startOrUpdateNotification() {
if (player != null) {
Notification notification = updateNotification(null); Notification notification = updateNotification(null);
if (!isNotificationStarted) { if (!isNotificationStarted) {
isNotificationStarted = true; isNotificationStarted = true;
@ -714,9 +717,10 @@ public class PlayerNotificationManager {
} }
} }
} }
}
private void maybeUpdateNotification() { private void maybeUpdateNotification() {
if (isNotificationStarted) { if (isNotificationStarted && player != null) {
updateNotification(null); updateNotification(null);
} }
} }
@ -732,64 +736,6 @@ public class PlayerNotificationManager {
} }
} }
private Map<String, NotificationCompat.Action> createPlaybackActions(Context context) {
Map<String, NotificationCompat.Action> actions = new HashMap<>();
Intent playIntent = new Intent(ACTION_PLAY).setPackage(context.getPackageName());
actions.put(
ACTION_PLAY,
new NotificationCompat.Action(
R.drawable.exo_notification_play,
context.getString(R.string.exo_controls_play_description),
PendingIntent.getBroadcast(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent pauseIntent = new Intent(ACTION_PAUSE).setPackage(context.getPackageName());
actions.put(
ACTION_PAUSE,
new NotificationCompat.Action(
R.drawable.exo_notification_pause,
context.getString(R.string.exo_controls_pause_description),
PendingIntent.getBroadcast(
context, 0, pauseIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent stopIntent = new Intent(ACTION_STOP).setPackage(context.getPackageName());
actions.put(
ACTION_STOP,
new NotificationCompat.Action(
R.drawable.exo_notification_stop,
context.getString(R.string.exo_controls_stop_description),
PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent rewindIntent = new Intent(ACTION_REWIND).setPackage(context.getPackageName());
actions.put(
ACTION_REWIND,
new NotificationCompat.Action(
R.drawable.exo_notification_rewind,
context.getString(R.string.exo_controls_rewind_description),
PendingIntent.getBroadcast(
context, 0, rewindIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent fastForwardIntent = new Intent(ACTION_FAST_FORWARD).setPackage(context.getPackageName());
actions.put(
ACTION_FAST_FORWARD,
new NotificationCompat.Action(
R.drawable.exo_notification_fastforward,
context.getString(R.string.exo_controls_fastforward_description),
PendingIntent.getBroadcast(
context, 0, fastForwardIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent previousIntent = new Intent(ACTION_PREVIOUS).setPackage(context.getPackageName());
actions.put(
ACTION_PREVIOUS,
new NotificationCompat.Action(
R.drawable.exo_notification_previous,
context.getString(R.string.exo_controls_previous_description),
PendingIntent.getBroadcast(
context, 0, previousIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent nextIntent = new Intent(ACTION_NEXT).setPackage(context.getPackageName());
actions.put(
ACTION_NEXT,
new NotificationCompat.Action(
R.drawable.exo_notification_next,
context.getString(R.string.exo_controls_next_description),
PendingIntent.getBroadcast(context, 0, nextIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
return actions;
}
/** /**
* Creates the notification given the current player state. * Creates the notification given the current player state.
* *
@ -821,7 +767,7 @@ public class PlayerNotificationManager {
// Configure stop action (eg. when user dismisses the notification when !isOngoing). // Configure stop action (eg. when user dismisses the notification when !isOngoing).
boolean useStopAction = stopAction != null && !isPlayingAd; boolean useStopAction = stopAction != null && !isPlayingAd;
mediaStyle.setShowCancelButton(useStopAction); mediaStyle.setShowCancelButton(useStopAction);
if (useStopAction) { if (useStopAction && stopPendingIntent != null) {
builder.setDeleteIntent(stopPendingIntent); builder.setDeleteIntent(stopPendingIntent);
mediaStyle.setCancelButtonIntent(stopPendingIntent); mediaStyle.setCancelButtonIntent(stopPendingIntent);
} }
@ -905,7 +851,7 @@ public class PlayerNotificationManager {
if (useNavigationActions && player.getNextWindowIndex() != C.INDEX_UNSET) { if (useNavigationActions && player.getNextWindowIndex() != C.INDEX_UNSET) {
stringActions.add(ACTION_NEXT); stringActions.add(ACTION_NEXT);
} }
if (!customActions.isEmpty()) { if (customActionReceiver != null) {
stringActions.addAll(customActionReceiver.getCustomActions(player)); stringActions.addAll(customActionReceiver.getCustomActions(player));
} }
if (ACTION_STOP.equals(stopAction)) { if (ACTION_STOP.equals(stopAction)) {
@ -932,6 +878,64 @@ public class PlayerNotificationManager {
return new int[] {actionIndex}; return new int[] {actionIndex};
} }
private static Map<String, NotificationCompat.Action> createPlaybackActions(Context context) {
Map<String, NotificationCompat.Action> actions = new HashMap<>();
Intent playIntent = new Intent(ACTION_PLAY).setPackage(context.getPackageName());
actions.put(
ACTION_PLAY,
new NotificationCompat.Action(
R.drawable.exo_notification_play,
context.getString(R.string.exo_controls_play_description),
PendingIntent.getBroadcast(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent pauseIntent = new Intent(ACTION_PAUSE).setPackage(context.getPackageName());
actions.put(
ACTION_PAUSE,
new NotificationCompat.Action(
R.drawable.exo_notification_pause,
context.getString(R.string.exo_controls_pause_description),
PendingIntent.getBroadcast(
context, 0, pauseIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent stopIntent = new Intent(ACTION_STOP).setPackage(context.getPackageName());
actions.put(
ACTION_STOP,
new NotificationCompat.Action(
R.drawable.exo_notification_stop,
context.getString(R.string.exo_controls_stop_description),
PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent rewindIntent = new Intent(ACTION_REWIND).setPackage(context.getPackageName());
actions.put(
ACTION_REWIND,
new NotificationCompat.Action(
R.drawable.exo_notification_rewind,
context.getString(R.string.exo_controls_rewind_description),
PendingIntent.getBroadcast(
context, 0, rewindIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent fastForwardIntent = new Intent(ACTION_FAST_FORWARD).setPackage(context.getPackageName());
actions.put(
ACTION_FAST_FORWARD,
new NotificationCompat.Action(
R.drawable.exo_notification_fastforward,
context.getString(R.string.exo_controls_fastforward_description),
PendingIntent.getBroadcast(
context, 0, fastForwardIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent previousIntent = new Intent(ACTION_PREVIOUS).setPackage(context.getPackageName());
actions.put(
ACTION_PREVIOUS,
new NotificationCompat.Action(
R.drawable.exo_notification_previous,
context.getString(R.string.exo_controls_previous_description),
PendingIntent.getBroadcast(
context, 0, previousIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
Intent nextIntent = new Intent(ACTION_NEXT).setPackage(context.getPackageName());
actions.put(
ACTION_NEXT,
new NotificationCompat.Action(
R.drawable.exo_notification_next,
context.getString(R.string.exo_controls_next_description),
PendingIntent.getBroadcast(context, 0, nextIntent, PendingIntent.FLAG_CANCEL_CURRENT)));
return actions;
}
private class PlayerListener extends Player.DefaultEventListener { private class PlayerListener extends Player.DefaultEventListener {
@Override @Override
@ -946,7 +950,7 @@ public class PlayerNotificationManager {
@Override @Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
if (player.getPlaybackState() == Player.STATE_IDLE) { if (player == null || player.getPlaybackState() == Player.STATE_IDLE) {
return; return;
} }
startOrUpdateNotification(); startOrUpdateNotification();
@ -954,7 +958,7 @@ public class PlayerNotificationManager {
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
if (player.getPlaybackState() == Player.STATE_IDLE) { if (player == null || player.getPlaybackState() == Player.STATE_IDLE) {
return; return;
} }
startOrUpdateNotification(); startOrUpdateNotification();
@ -967,7 +971,7 @@ public class PlayerNotificationManager {
@Override @Override
public void onRepeatModeChanged(int repeatMode) { public void onRepeatModeChanged(int repeatMode) {
if (player.getPlaybackState() == Player.STATE_IDLE) { if (player == null || player.getPlaybackState() == Player.STATE_IDLE) {
return; return;
} }
startOrUpdateNotification(); startOrUpdateNotification();
@ -985,7 +989,8 @@ public class PlayerNotificationManager {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (!isNotificationStarted) { Player player = PlayerNotificationManager.this.player;
if (player == null || !isNotificationStarted) {
return; return;
} }
String action = intent.getAction(); String action = intent.getAction();
@ -1013,7 +1018,7 @@ public class PlayerNotificationManager {
} else if (ACTION_STOP.equals(action)) { } else if (ACTION_STOP.equals(action)) {
controlDispatcher.dispatchStop(player, true); controlDispatcher.dispatchStop(player, true);
stopNotification(); stopNotification();
} else if (customActions.containsKey(action)) { } else if (customActionReceiver != null && customActions.containsKey(action)) {
customActionReceiver.onCustomAction(player, action, intent); customActionReceiver.onCustomAction(player, action, intent);
} }
} }