From 57f39a2a875ada75ef6f6d5998d72cde3bcc3169 Mon Sep 17 00:00:00 2001 From: Brian Wernick Date: Sat, 20 Feb 2016 11:23:33 -0700 Subject: [PATCH] Added null checks, minor helper cleanup to avoid null cases --- .../android/exomedia/EMLockScreen.java | 44 ++++----- .../android/exomedia/EMNotification.java | 57 ++++++----- .../exomedia/service/EMPlaylistService.java | 94 +++++++++++++------ 3 files changed, 116 insertions(+), 79 deletions(-) diff --git a/library/src/main/java/com/devbrackets/android/exomedia/EMLockScreen.java b/library/src/main/java/com/devbrackets/android/exomedia/EMLockScreen.java index c35e8154..1df7b2eb 100644 --- a/library/src/main/java/com/devbrackets/android/exomedia/EMLockScreen.java +++ b/library/src/main/java/com/devbrackets/android/exomedia/EMLockScreen.java @@ -24,6 +24,8 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; @@ -40,29 +42,29 @@ public class EMLockScreen { public static final String SESSION_TAG = "EMLockScreen.Session"; public static final String RECEIVER_EXTRA_CLASS = "com.devbrackets.android.exomedia.RECEIVER_EXTRA_CLASS"; + @NonNull private Context context; - private Class mediaServiceClass; - - private boolean showLockScreen = true; - + @Nullable private Bitmap appIconBitmap; + @Nullable private MediaSessionCompat mediaSession; + private boolean showLockScreen = true; + /** * Creates a new EMLockScreen object * * @param context The context to use for holding a MediaSession and sending action intents * @param mediaServiceClass The class for the service that owns the backing MediaService and to notify of playback actions */ - public EMLockScreen(Context context, Class mediaServiceClass) { + public EMLockScreen(@NonNull Context context, @NonNull Class mediaServiceClass) { this.context = context; - this.mediaServiceClass = mediaServiceClass; ComponentName componentName = new ComponentName(context, MediaControlsReceiver.class.getName()); - mediaSession = new MediaSessionCompat(context, SESSION_TAG, componentName, getMediaButtonReceiverPendingIntent(componentName)); + mediaSession = new MediaSessionCompat(context, SESSION_TAG, componentName, getMediaButtonReceiverPendingIntent(componentName, mediaServiceClass)); mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); - mediaSession.setCallback(new SessionCallback()); + mediaSession.setCallback(new SessionCallback(mediaServiceClass)); } public void release() { @@ -70,10 +72,7 @@ public void release() { mediaSession.release(); } - context = null; appIconBitmap = null; - mediaServiceClass = null; - mediaServiceClass = null; } /** @@ -92,7 +91,7 @@ public void setLockScreenEnabled(boolean enabled) { showLockScreen = enabled; //Remove the lock screen when disabling - if (!enabled) { + if (!enabled && mediaSession != null) { mediaSession.setActive(false); } } @@ -135,8 +134,9 @@ public void updateLockScreenInformation(String title, String album, String artis metaDataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, mediaArtwork); } - mediaSession.setMetadata(metaDataBuilder.build()); - + if (mediaSession != null) { + mediaSession.setMetadata(metaDataBuilder.build()); + } //Updates the available playback controls PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder(); @@ -151,11 +151,11 @@ public void updateLockScreenInformation(String title, String album, String artis } } - private PendingIntent getMediaButtonReceiverPendingIntent(ComponentName componentName) { + private PendingIntent getMediaButtonReceiverPendingIntent(ComponentName componentName, @NonNull Class serviceClass) { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.setComponent(componentName); - mediaButtonIntent.putExtra(RECEIVER_EXTRA_CLASS, mediaServiceClass.getName()); + mediaButtonIntent.putExtra(RECEIVER_EXTRA_CLASS, serviceClass.getName()); return PendingIntent.getBroadcast(context, 0, mediaButtonIntent, PendingIntent.FLAG_CANCEL_CURRENT); } @@ -192,7 +192,7 @@ private long getPlaybackOptions(EMNotification.NotificationMediaState mediaState * @param serviceClass The service class to notify of intents * @return The resulting PendingIntent */ - private PendingIntent createPendingIntent(String action, Class serviceClass) { + private PendingIntent createPendingIntent(@NonNull String action, @NonNull Class serviceClass) { Intent intent = new Intent(context, serviceClass); intent.setAction(action); @@ -201,17 +201,17 @@ private PendingIntent createPendingIntent(String action, Class serviceClass) { super(); - playPausePendingIntent = createPendingIntent(EMRemoteActions.ACTION_PLAY_PAUSE, mediaServiceClass); - nextPendingIntent = createPendingIntent(EMRemoteActions.ACTION_NEXT, mediaServiceClass); - previousPendingIntent = createPendingIntent(EMRemoteActions.ACTION_PREVIOUS, mediaServiceClass); + playPausePendingIntent = createPendingIntent(EMRemoteActions.ACTION_PLAY_PAUSE, serviceClass); + nextPendingIntent = createPendingIntent(EMRemoteActions.ACTION_NEXT, serviceClass); + previousPendingIntent = createPendingIntent(EMRemoteActions.ACTION_PREVIOUS, serviceClass); } @Override diff --git a/library/src/main/java/com/devbrackets/android/exomedia/EMNotification.java b/library/src/main/java/com/devbrackets/android/exomedia/EMNotification.java index 63f303d2..49691218 100644 --- a/library/src/main/java/com/devbrackets/android/exomedia/EMNotification.java +++ b/library/src/main/java/com/devbrackets/android/exomedia/EMNotification.java @@ -26,6 +26,7 @@ import android.graphics.Bitmap; import android.os.Build; import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.view.View; @@ -36,16 +37,17 @@ * media playback applications. */ public class EMNotification { + @NonNull private Context context; - private NotificationManager notificationManager; + @NonNull private NotificationInfo notificationInfo = new NotificationInfo(); + @Nullable + private NotificationManager notificationManager; + @Nullable private Class mediaServiceClass; - private RemoteViews customNotification; - private RemoteViews bigContent; - - public EMNotification(Context context) { + public EMNotification(@NonNull Context context) { this.context = context; notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } @@ -58,9 +60,6 @@ public void release() { dismiss(); mediaServiceClass = null; - customNotification = null; - bigContent = null; - notificationInfo.clean(); } @@ -68,7 +67,7 @@ public void release() { * Dismisses the current active notification */ public void dismiss() { - if (notificationManager != null && notificationInfo != null) { + if (notificationManager != null) { notificationManager.cancel(notificationInfo.getNotificationId()); } } @@ -78,7 +77,7 @@ public void dismiss() { * ready for playback (e.g. paused). The notification information * will need to be updated by calling {@link #setNotificationBaseInformation(int, int)} * and {@link #updateNotificationInformation(String, String, String, Bitmap, Bitmap)} and can be retrieved - * with {@link #getNotification(android.app.PendingIntent)} + * with {@link #getNotification(android.app.PendingIntent, Class)} * * @param enabled True if notifications should be shown */ @@ -90,7 +89,7 @@ public void setNotificationsEnabled(boolean enabled) { notificationInfo.setShowNotifications(enabled); //Remove the notification when disabling - if (!enabled) { + if (!enabled && notificationManager != null) { notificationManager.cancel(notificationInfo.getNotificationId()); } } @@ -171,8 +170,8 @@ public void updateNotificationInformation(@Nullable String title, @Nullable Stri notificationInfo.setSecondaryImage(secondaryNotificationImage); notificationInfo.setMediaState(notificationMediaState); - if (notificationInfo.getShowNotifications()) { - notificationManager.notify(notificationInfo.getNotificationId(), getNotification(notificationInfo.getPendingIntent())); + if (notificationInfo.getShowNotifications() && notificationManager != null && mediaServiceClass != null) { + notificationManager.notify(notificationInfo.getNotificationId(), getNotification(notificationInfo.getPendingIntent(), mediaServiceClass)); } } @@ -185,16 +184,16 @@ public void updateNotificationInformation(@Nullable String title, @Nullable Stri * @return The constructed notification */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public Notification getNotification(@Nullable PendingIntent pendingIntent) { + public Notification getNotification(@Nullable PendingIntent pendingIntent, @NonNull Class serviceClass) { setClickPendingIntent(pendingIntent); - RemoteViews customNotificationViews = getCustomNotification(); + RemoteViews customNotificationViews = getCustomNotification(serviceClass); boolean allowSwipe = notificationInfo.getMediaState() == null || !notificationInfo.getMediaState().isPlaying(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder.setContent(customNotificationViews); builder.setContentIntent(pendingIntent); - builder.setDeleteIntent(createPendingIntent(EMRemoteActions.ACTION_STOP, mediaServiceClass)); + builder.setDeleteIntent(createPendingIntent(EMRemoteActions.ACTION_STOP, serviceClass)); builder.setSmallIcon(notificationInfo.getAppIcon()); builder.setAutoCancel(allowSwipe); builder.setOngoing(!allowSwipe); @@ -212,7 +211,7 @@ public Notification getNotification(@Nullable PendingIntent pendingIntent) { //Build the notification and set the expanded content view if there is a service to inform of clicks Notification notification = builder.build(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mediaServiceClass != null) { - notification.bigContentView = getBigNotification(); + notification.bigContentView = getBigNotification(serviceClass); notification.bigContentView.setOnClickPendingIntent(R.id.exomedia_big_notification_touch_area, pendingIntent); } @@ -224,12 +223,12 @@ public Notification getNotification(@Nullable PendingIntent pendingIntent) { * * @return The resulting RemoteViews */ - private RemoteViews getCustomNotification() { - customNotification = new RemoteViews(context.getPackageName(), R.layout.exomedia_notification_content); + private RemoteViews getCustomNotification(@NonNull Class serviceClass) { + RemoteViews customNotification = new RemoteViews(context.getPackageName(), R.layout.exomedia_notification_content); - customNotification.setOnClickPendingIntent(R.id.exomedia_notification_playpause, createPendingIntent(EMRemoteActions.ACTION_PLAY_PAUSE, mediaServiceClass)); - customNotification.setOnClickPendingIntent(R.id.exomedia_notification_next, createPendingIntent(EMRemoteActions.ACTION_NEXT, mediaServiceClass)); - customNotification.setOnClickPendingIntent(R.id.exomedia_notification_prev, createPendingIntent(EMRemoteActions.ACTION_PREVIOUS, mediaServiceClass)); + customNotification.setOnClickPendingIntent(R.id.exomedia_notification_playpause, createPendingIntent(EMRemoteActions.ACTION_PLAY_PAUSE, serviceClass)); + customNotification.setOnClickPendingIntent(R.id.exomedia_notification_next, createPendingIntent(EMRemoteActions.ACTION_NEXT, serviceClass)); + customNotification.setOnClickPendingIntent(R.id.exomedia_notification_prev, createPendingIntent(EMRemoteActions.ACTION_PREVIOUS, serviceClass)); customNotification.setTextViewText(R.id.exomedia_notification_title, notificationInfo.getTitle()); customNotification.setTextViewText(R.id.exomedia_notification_album, notificationInfo.getAlbum()); @@ -250,13 +249,13 @@ private RemoteViews getCustomNotification() { * * @return The resulting RemoteViews */ - private RemoteViews getBigNotification() { - bigContent = new RemoteViews(context.getPackageName(), R.layout.exomedia_big_notification_content); + private RemoteViews getBigNotification(@NonNull Class serviceClass) { + RemoteViews bigContent = new RemoteViews(context.getPackageName(), R.layout.exomedia_big_notification_content); - bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_close, createPendingIntent(EMRemoteActions.ACTION_STOP, mediaServiceClass)); - bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_playpause, createPendingIntent(EMRemoteActions.ACTION_PLAY_PAUSE, mediaServiceClass)); - bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_next, createPendingIntent(EMRemoteActions.ACTION_NEXT, mediaServiceClass)); - bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_prev, createPendingIntent(EMRemoteActions.ACTION_PREVIOUS, mediaServiceClass)); + bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_close, createPendingIntent(EMRemoteActions.ACTION_STOP, serviceClass)); + bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_playpause, createPendingIntent(EMRemoteActions.ACTION_PLAY_PAUSE, serviceClass)); + bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_next, createPendingIntent(EMRemoteActions.ACTION_NEXT, serviceClass)); + bigContent.setOnClickPendingIntent(R.id.exomedia_big_notification_prev, createPendingIntent(EMRemoteActions.ACTION_PREVIOUS, serviceClass)); bigContent.setTextViewText(R.id.exomedia_big_notification_title, notificationInfo.getTitle()); bigContent.setTextViewText(R.id.exomedia_big_notification_album, notificationInfo.getAlbum()); @@ -313,7 +312,7 @@ private void updateBigNotificationMediaState(RemoteViews bigContent) { * @param serviceClass The service class to notify of intents * @return The resulting PendingIntent */ - private PendingIntent createPendingIntent(String action, Class serviceClass) { + private PendingIntent createPendingIntent(String action, @NonNull Class serviceClass) { Intent intent = new Intent(context, serviceClass); intent.setAction(action); diff --git a/library/src/main/java/com/devbrackets/android/exomedia/service/EMPlaylistService.java b/library/src/main/java/com/devbrackets/android/exomedia/service/EMPlaylistService.java index 0d6cc0c9..98d6e9c7 100644 --- a/library/src/main/java/com/devbrackets/android/exomedia/service/EMPlaylistService.java +++ b/library/src/main/java/com/devbrackets/android/exomedia/service/EMPlaylistService.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.PowerManager; import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; @@ -74,27 +75,35 @@ public enum MediaState { } protected WifiManager.WifiLock wifiLock; + @Nullable protected EMAudioFocusHelper audioFocusHelper; + @Nullable protected EMAudioPlayer audioPlayer; + @Nullable protected EMMediaProgressEvent currentMediaProgress; + @Nullable protected EMNotification notificationHelper; + @Nullable protected EMLockScreen lockScreenHelper; protected boolean pausedForFocusLoss = false; protected MediaState currentState = MediaState.PREPARING; + @Nullable protected I currentPlaylistItem; protected int seekToPosition = -1; protected boolean immediatelyPause = false; + @NonNull protected AudioListener audioListener = new AudioListener(); protected boolean pausedForSeek = false; protected boolean foregroundSetup; protected boolean notificationSetup; protected boolean onCreateCalled = false; + @Nullable protected Intent workaroundIntent = null; @Nullable @@ -102,6 +111,7 @@ public enum MediaState { @Nullable protected String currentLockScreenArtworkUrl; + @NonNull protected List callbackList = new LinkedList<>(); /** @@ -380,10 +390,12 @@ public void onDestroy() { relaxResources(true); getMediaPlaylistManager().unRegisterService(); - audioFocusHelper.setAudioFocusCallback(null); - audioFocusHelper.abandonFocus(); - audioFocusHelper = null; + if (audioFocusHelper != null) { + audioFocusHelper.setAudioFocusCallback(null); + audioFocusHelper = null; + } + notificationHelper = null; lockScreenHelper = null; @@ -427,7 +439,7 @@ public void onTaskRemoved(Intent rootIntent) { */ @Override public boolean onAudioFocusGained() { - if (!currentItemIsAudio()) { + if (!currentItemIsAudio() || audioPlayer == null) { return false; } @@ -446,12 +458,12 @@ public boolean onAudioFocusGained() { */ @Override public boolean onAudioFocusLost(boolean canDuckAudio) { - if (!currentItemIsAudio()) { + if (!currentItemIsAudio() || audioPlayer == null) { return false; } - if (audioFocusHelper.getCurrentAudioFocus() == EMAudioFocusHelper.Focus.NO_FOCUS_NO_DUCK) { - if (audioPlayer.isPlaying()) { + if (!canDuckAudio) { + if (audioPlayer.isPlaying() ) { pausedForFocusLoss = true; audioPlayer.pause(); updateNotification(); @@ -548,6 +560,7 @@ protected void onServiceCreate() { audioFocusHelper = new EMAudioFocusHelper(getApplicationContext()); audioFocusHelper.setAudioFocusCallback(this); wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "mcLock"); + wifiLock.setReferenceCounted(false); notificationHelper = new EMNotification(getApplicationContext()); lockScreenHelper = new EMLockScreen(getApplicationContext(), getClass()); @@ -642,8 +655,14 @@ protected void performMediaCompletion() { * {@link EMPlaylistManager#invokeSeekStarted()} */ protected void performSeekStarted() { - EMVideoView videoView = getMediaPlaylistManager().getVideoView(); - boolean isPlaying = (currentItemIsAudio() && audioPlayer.isPlaying()) || (currentItemIsVideo() && videoView != null && videoView.isPlaying()); + boolean isPlaying = false; + + if (currentItemIsAudio()) { + isPlaying = audioPlayer != null && audioPlayer.isPlaying(); + } else if (currentItemIsVideo()) { + EMVideoView videoView = getMediaPlaylistManager().getVideoView(); + isPlaying = videoView != null && videoView.isPlaying(); + } if (isPlaying) { pausedForSeek = true; @@ -732,7 +751,6 @@ protected void performStop() { // let go of all resources relaxResources(true); - audioFocusHelper.abandonFocus(); //Cleans out the avPlayListManager getMediaPlaylistManager().setParameters(null, 0); @@ -748,10 +766,8 @@ protected void performStop() { * @param position The position to seek to in milliseconds */ protected void performSeek(int position) { - if (currentItemIsAudio()) { - if (audioPlayer != null) { - audioPlayer.seekTo(position); - } + if (currentItemIsAudio() && audioPlayer != null) { + audioPlayer.seekTo(position); } else if (currentItemIsVideo()) { EMVideoView videoView = getMediaPlaylistManager().getVideoView(); if (videoView != null) { @@ -850,9 +866,9 @@ protected void onLockScreenArtworkUpdated() { * Sets up the service as a Foreground service only if we aren't already registered as such */ protected void setupForeground() { - if (!foregroundSetup && notificationSetup) { + if (!foregroundSetup && notificationSetup && notificationHelper != null) { foregroundSetup = true; - startForeground(getNotificationId(), notificationHelper.getNotification(getNotificationClickPendingIntent())); + startForeground(getNotificationId(), notificationHelper.getNotification(getNotificationClickPendingIntent(), getClass())); } } @@ -904,10 +920,15 @@ protected void startItemPlayback() { protected void playAudioItem() { stopVideoPlayback(); initializeAudioPlayer(); - audioFocusHelper.requestFocus(); + if (audioFocusHelper != null) { + audioFocusHelper.requestFocus(); + } - boolean isItemDownloaded = isDownloaded(currentPlaylistItem); + //noinspection ConstantConditions - currentPlaylistItem is not null at this point (see calling method for null check) audioPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + boolean isItemDownloaded = isDownloaded(currentPlaylistItem); + + //noinspection ConstantConditions - currentPlaylistItem is not null at this point (see calling method for null check) audioPlayer.setDataSource(this, Uri.parse(isItemDownloaded ? currentPlaylistItem.getDownloadedMediaUri() : currentPlaylistItem.getMediaUrl())); setMediaState(MediaState.PREPARING); @@ -936,6 +957,8 @@ protected void playVideoItem() { if (videoView != null) { videoView.stopPlayback(); boolean isItemDownloaded = isDownloaded(currentPlaylistItem); + + //noinspection ConstantConditions - currentPlaylistItem is not null at this point (see calling method for null check) videoView.setVideoURI(Uri.parse(isItemDownloaded ? currentPlaylistItem.getDownloadedMediaUri() : currentPlaylistItem.getMediaUrl())); } } @@ -951,7 +974,9 @@ protected void playOtherItem() { * Stops the AudioPlayer from playing. */ protected void stopAudioPlayback() { - audioFocusHelper.abandonFocus(); + if (audioFocusHelper != null) { + audioFocusHelper.abandonFocus(); + } if (audioPlayer != null) { audioPlayer.stopPlayback(); @@ -982,7 +1007,7 @@ protected void startAudioPlayer() { return; } - if (audioFocusHelper.getCurrentAudioFocus() == EMAudioFocusHelper.Focus.NO_FOCUS_NO_DUCK) { + if (audioFocusHelper == null ||audioFocusHelper.getCurrentAudioFocus() == EMAudioFocusHelper.Focus.NO_FOCUS_NO_DUCK) { // If we don't have audio focus and can't duck we have to pause, even if state is playing // Be we stay in the playing state so we know we have to resume playback once we get the focus back. if (audioPlayer.isPlaying()) { @@ -1012,8 +1037,14 @@ protected void startAudioPlayer() { protected void relaxResources(boolean releaseAudioPlayer) { stopForeground(true); foregroundSetup = false; - notificationHelper.release(); - lockScreenHelper.release(); + + if (notificationHelper != null) { + notificationHelper.release(); + } + + if (lockScreenHelper != null) { + lockScreenHelper.release(); + } notificationSetup = false; @@ -1030,6 +1061,10 @@ protected void relaxResources(boolean releaseAudioPlayer) { if (wifiLock.isHeld()) { wifiLock.release(); } + + if (audioFocusHelper != null) { + audioFocusHelper.abandonFocus(); + } } /** @@ -1075,12 +1110,16 @@ protected void seekToNextPlayableItem() { */ protected void setupAsForeground() { //Sets up the Lock Screen playback controls - lockScreenHelper.setLockScreenEnabled(true); - lockScreenHelper.setLockScreenBaseInformation(getLockScreenIconRes()); + if (lockScreenHelper != null) { + lockScreenHelper.setLockScreenEnabled(true); + lockScreenHelper.setLockScreenBaseInformation(getLockScreenIconRes()); + } //Sets up the Notifications - notificationHelper.setNotificationsEnabled(true); - notificationHelper.setNotificationBaseInformation(getNotificationId(), getNotificationIconRes(), getClass()); + if (notificationHelper != null) { + notificationHelper.setNotificationsEnabled(true); + notificationHelper.setNotificationBaseInformation(getNotificationId(), getNotificationIconRes(), getClass()); + } //Starts the service as the foreground audio player notificationSetup = true; @@ -1289,7 +1328,6 @@ public boolean onError(MediaPlayer mp, int what, int extra) { setMediaState(MediaState.ERROR); relaxResources(true); - audioFocusHelper.abandonFocus(); return false; } @@ -1307,7 +1345,7 @@ public void onPrepared(MediaPlayer mp) { //Immediately pauses if (immediatelyPause) { immediatelyPause = false; - if (audioPlayer.isPlaying()) { + if (audioPlayer != null && audioPlayer.isPlaying()) { performPause(); } }