From 0691bb80c0726485f9db69c6f065eda9009164ee Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Mon, 15 Jun 2015 20:51:55 +0200 Subject: [PATCH] Completely migrated from RemoteControlClient to MediaSession. Allows persistent notification to be swiped away when music is not playing (#33). --- .../gramophone/helper/MediaSessionHelper.java | 70 +++++ .../helper/PlayingNotificationHelper.java | 240 ++++++------------ .../gramophone/service/MusicService.java | 136 +++++----- 3 files changed, 222 insertions(+), 224 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/helper/MediaSessionHelper.java diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/MediaSessionHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/MediaSessionHelper.java new file mode 100644 index 00000000..373a0ef3 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/MediaSessionHelper.java @@ -0,0 +1,70 @@ +package com.kabouzeid.gramophone.helper; + +import android.media.RemoteControlClient; +import android.os.Build; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; + +public class MediaSessionHelper { + + public static void applyState(MediaSessionCompat session, PlaybackStateCompat playbackState) { + session.setPlaybackState(playbackState); + ensureTransportControls(session, playbackState); + } + + private static void ensureTransportControls(MediaSessionCompat session, PlaybackStateCompat playbackState) { + long actions = playbackState.getActions(); + Object rccObj = session.getRemoteControlClient(); + + if (actions != 0 + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH + && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP + && rccObj != null) { + + int transportControls = 0; + + if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS; + } + + if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_REWIND; + } + + if ((actions & PlaybackStateCompat.ACTION_PLAY) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PLAY; + } + + if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; + } + + if ((actions & PlaybackStateCompat.ACTION_PAUSE) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PAUSE; + } + + if ((actions & PlaybackStateCompat.ACTION_STOP) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_STOP; + } + + if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD; + } + + if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_NEXT; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if ((actions & PlaybackStateCompat.ACTION_SEEK_TO) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE; + } + + if ((actions & PlaybackStateCompat.ACTION_SET_RATING) != 0) { + transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_RATING; + } + } + ((RemoteControlClient) rccObj).setTransportControlFlags(transportControls); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java index 99a5e777..a84aa05e 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java @@ -5,16 +5,15 @@ package com.kabouzeid.gramophone.helper; */ import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.v4.media.session.MediaSessionCompat; import android.support.v7.app.NotificationCompat; -import android.view.View; -import android.widget.RemoteViews; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.model.Song; @@ -22,192 +21,101 @@ import com.kabouzeid.gramophone.service.MusicService; import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity; import com.kabouzeid.gramophone.util.MusicUtil; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.assist.ImageSize; -import com.nostra13.universalimageloader.core.assist.ViewScaleType; -import com.nostra13.universalimageloader.core.imageaware.NonViewAware; -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; public class PlayingNotificationHelper { public static final String TAG = PlayingNotificationHelper.class.getSimpleName(); - public static final int NOTIFICATION_ID = 1337; - private final MusicService service; + public static Notification buildNotification(final Context context, MediaSessionCompat.Token sessionToken, final Song song, final boolean isPlaying) { - private final NotificationManager notificationManager; - private Notification notification = null; + NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle() + .setMediaSession(sessionToken) + .setShowActionsInCompactView(0, 1, 2); - private RemoteViews notificationLayout; - private RemoteViews notificationLayoutExpanded; + style.setShowCancelButton(true); + style.setCancelButtonIntent(retrievePlaybackAction(context, 3)); - private Song currentSong; - private String currentAlbumArtUri; + Bitmap albumArt = ImageLoader.getInstance().loadImageSync(MusicUtil.getAlbumArtUri(song.albumId).toString()); + if (albumArt == null) { + albumArt = BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); + } - public PlayingNotificationHelper(final MusicService service) { - this.service = service; - notificationManager = (NotificationManager) service - .getSystemService(Context.NOTIFICATION_SERVICE); - } - - public void buildNotification(final Song song, final boolean isPlaying) { - currentSong = song; - notificationLayout = new RemoteViews(service.getPackageName(), - R.layout.notification_playing); - notificationLayoutExpanded = new RemoteViews(service.getPackageName(), - R.layout.notification_playing_expanded); - - notification = new NotificationCompat.Builder(service) + return new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notification) - .setContentIntent(getOpenMusicControllerPendingIntent()) - .setCategory(NotificationCompat.CATEGORY_PROGRESS) - .setPriority(NotificationCompat.PRIORITY_MAX) + .setLargeIcon(albumArt) + .setContentIntent(getOpenMusicControllerPendingIntent(context)) + .setContentTitle(song.title) + .setContentText(song.artistName) + .setSubText(song.albumName) + .setWhen(0) + .setShowWhen(false) + .setStyle(style) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setContent(notificationLayout) - .setStyle(new NotificationCompat.MediaStyle()) + + .addAction(R.drawable.ic_skip_previous_white_36dp, + "", + retrievePlaybackAction(context, 2)) + + .addAction(isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp, + "", + retrievePlaybackAction(context, 0)) + + .addAction(R.drawable.ic_skip_next_white_36dp, + "", + retrievePlaybackAction(context, 1)) + + .setOnlyAlertOnce(true) .build(); - notification.bigContentView = notificationLayoutExpanded; - - setUpCollapsedLayout(); - setUpExpandedLayout(); - loadAlbumArt(); - setUpPlaybackActions(isPlaying); - setUpExpandedPlaybackActions(isPlaying); - - service.startForeground(NOTIFICATION_ID, notification); } - private PendingIntent getOpenMusicControllerPendingIntent() { - Intent result = new Intent(service, MusicControllerActivity.class); - TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(service); + private static PendingIntent getOpenMusicControllerPendingIntent(final Context context) { + Intent result = new Intent(context, MusicControllerActivity.class); + TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context); taskStackBuilder.addParentStack(MusicControllerActivity.class); taskStackBuilder.addNextIntent(result); return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); } - private void setUpExpandedPlaybackActions(boolean isPlaying) { - notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_toggle_play_pause, - retrievePlaybackActions(1)); - - notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_next, - retrievePlaybackActions(2)); - - notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_prev, - retrievePlaybackActions(3)); - - notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_quit, - retrievePlaybackActions(4)); - - notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_play_pause, - isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); - } - - private void setUpPlaybackActions(boolean isPlaying) { - notificationLayout.setOnClickPendingIntent(R.id.button_toggle_play_pause, - retrievePlaybackActions(1)); - - notificationLayout.setOnClickPendingIntent(R.id.button_next, - retrievePlaybackActions(2)); - - notificationLayout.setOnClickPendingIntent(R.id.button_quit, - retrievePlaybackActions(4)); - - notificationLayout.setImageViewResource(R.id.button_toggle_play_pause, - isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); - } - - private PendingIntent retrievePlaybackActions(final int which) { - Intent action; - PendingIntent pendingIntent; - final ComponentName serviceName = new ComponentName(service, MusicService.class); + private static PendingIntent retrievePlaybackAction(final Context context, final int which) { + String actionString = null; switch (which) { - case 1: - action = new Intent(MusicService.ACTION_TOGGLE_PLAYBACK); - action.setComponent(serviceName); - pendingIntent = PendingIntent.getService(service, 1, action, 0); - return pendingIntent; - case 2: - action = new Intent(MusicService.ACTION_SKIP); - action.setComponent(serviceName); - pendingIntent = PendingIntent.getService(service, 2, action, 0); - return pendingIntent; - case 3: - action = new Intent(MusicService.ACTION_REWIND); - action.setComponent(serviceName); - pendingIntent = PendingIntent.getService(service, 3, action, 0); - return pendingIntent; - case 4: - action = new Intent(MusicService.ACTION_QUIT); - action.setComponent(serviceName); - pendingIntent = PendingIntent.getService(service, 4, action, 0); - return pendingIntent; - default: + case 0: + actionString = MusicService.ACTION_TOGGLE_PLAYBACK; break; + case 1: + actionString = MusicService.ACTION_SKIP; + break; + case 2: + actionString = MusicService.ACTION_REWIND; + break; + case 3: + actionString = MusicService.ACTION_QUIT; + break; + } + if (actionString != null) { + final ComponentName serviceName = new ComponentName(context, MusicService.class); + Intent actionIntent = new Intent(actionString); + actionIntent.setComponent(serviceName); + return PendingIntent.getService(context, 0, actionIntent, 0); } return null; } - private void setUpCollapsedLayout() { - if (currentSong != null) { - notificationLayout.setTextViewText(R.id.song_title, currentSong.title); - notificationLayout.setTextViewText(R.id.song_artist, currentSong.artistName); - } - } - - private void setUpExpandedLayout() { - if (currentSong != null) { - notificationLayoutExpanded.setTextViewText(R.id.song_title, currentSong.title); - notificationLayoutExpanded.setTextViewText(R.id.song_artist, currentSong.artistName); - notificationLayoutExpanded.setTextViewText(R.id.album_title, currentSong.albumName); - } - } - - private void loadAlbumArt() { - currentAlbumArtUri = MusicUtil.getAlbumArtUri(currentSong.albumId).toString(); - ImageLoader.getInstance().displayImage(currentAlbumArtUri, new NonViewAware(new ImageSize(-1, -1), ViewScaleType.CROP), new SimpleImageLoadingListener() { - @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { - if (currentAlbumArtUri.equals(imageUri)) - // copy() prevents the original bitmap in the memory cache from being recycled by the remote views - setAlbumArt(loadedImage.copy(loadedImage.getConfig(), true)); - } - - @Override - public void onLoadingFailed(String imageUri, View view, FailReason failReason) { - if (currentAlbumArtUri.equals(imageUri)) - setAlbumArt(null); - } - }); - } - - private void setAlbumArt(Bitmap albumArt) { - if (albumArt != null) { - notificationLayout.setImageViewBitmap(R.id.album_art, albumArt); - notificationLayoutExpanded.setImageViewBitmap(R.id.album_art, albumArt); - } else { - notificationLayout.setImageViewResource(R.id.album_art, R.drawable.default_album_art); - notificationLayoutExpanded.setImageViewResource(R.id.album_art, R.drawable.default_album_art); - } - notificationManager.notify(NOTIFICATION_ID, notification); - } - - public void killNotification() { - service.stopForeground(true); - notification = null; - } - - public void updatePlayState(final boolean isPlaying) { - if (notification == null || notificationManager == null) { - return; - } - if (notificationLayout != null) { - notificationLayout.setImageViewResource(R.id.button_toggle_play_pause, - isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); - } - if (notificationLayoutExpanded != null) { - notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_play_pause, - isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); - } - notificationManager.notify(NOTIFICATION_ID, notification); - } +// private void loadAlbumArt() { +// currentAlbumArtUri = MusicUtil.getAlbumArtUri(currentSong.albumId).toString(); +// ImageLoader.getInstance().displayImage(currentAlbumArtUri, new NonViewAware(new ImageSize(-1, -1), ViewScaleType.CROP), new SimpleImageLoadingListener() { +// @Override +// public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { +// if (currentAlbumArtUri.equals(imageUri)) +// // copy() prevents the original bitmap in the memory cache from being recycled by the remote views +// setAlbumArt(loadedImage.copy(loadedImage.getConfig(), true)); +// } +// +// @Override +// public void onLoadingFailed(String imageUri, View view, FailReason failReason) { +// if (currentAlbumArtUri.equals(imageUri)) +// setAlbumArt(null); +// } +// }); +// } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index 86cdf726..5a964b58 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -1,5 +1,7 @@ package com.kabouzeid.gramophone.service; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; @@ -10,10 +12,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.media.AudioManager; -import android.media.MediaMetadata; import android.media.MediaPlayer; import android.media.audiofx.AudioEffect; -import android.media.session.PlaybackState; import android.net.Uri; import android.os.Binder; import android.os.Handler; @@ -33,6 +33,7 @@ import android.widget.Toast; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.appwidget.MusicPlayerWidget; +import com.kabouzeid.gramophone.helper.MediaSessionHelper; import com.kabouzeid.gramophone.helper.PlayingNotificationHelper; import com.kabouzeid.gramophone.helper.ShuffleHelper; import com.kabouzeid.gramophone.misc.AppKeys; @@ -56,6 +57,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe public static final String PHONOGRAPH_PACKAGE_NAME = "com.kabouzeid.gramophone"; public static final String MUSIC_PACKAGE_NAME = "com.android.music"; + public static final int NOTIFICATION_ID = 1337; + public static final String ACTION_TOGGLE_PLAYBACK = "com.kabouzeid.gramophone.action.TOGGLE_PLAYBACK"; public static final String ACTION_PLAY = "com.kabouzeid.gramophone.action.PLAY"; public static final String ACTION_RESUME = "com.kabouzeid.gramophone.action.RESUME"; @@ -69,7 +72,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe public static final String PLAYSTATE_CHANGED = "com.kabouzeid.gramophone.playstatechanged"; public static final String REPEATMODE_CHANGED = "com.kabouzeid.gramophone.repeatmodechanged"; public static final String SHUFFLEMODE_CHANGED = "com.kabouzeid.gramophone.shufflemodechanged"; - public static final String POSITION_CHANGED = "com.kabouzeid.phonograph.positionchanged"; + public static final String POSITION_IN_SONG_CHANGED = "com.kabouzeid.phonograph.positionchanged"; private static final int FOCUSCHANGE = 5; private static final int DUCK = 6; @@ -97,14 +100,14 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe private boolean thingsRegistered; private boolean saveQueuesAgain; private boolean isSavingQueues; - private PlayingNotificationHelper playingNotificationHelper; private AudioManager audioManager; - private MediaSessionCompat mSession; + private MediaSessionCompat mediaSession; private PowerManager.WakeLock wakeLock; private String currentAlbumArtUri; private MusicPlayerHandler playerHandler; private boolean fadingDown = false; private HandlerThread handlerThread; + private NotificationManager notificationManager; private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { @Override @@ -129,7 +132,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe isPlayerPrepared = false; playingQueue = new ArrayList<>(); originalPlayingQueue = new ArrayList<>(); - playingNotificationHelper = new PlayingNotificationHelper(this); shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_SHUFFLE_MODE, 0); repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_REPEAT_MODE, 0); @@ -143,18 +145,16 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe handlerThread.start(); playerHandler = new MusicPlayerHandler(this, handlerThread.getLooper()); - registerEverything(); + notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); setUpMediaSession(); + + registerEverything(); } private void setUpMediaSession() { - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); - PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); - - mSession = new MediaSessionCompat(this, "Phonograph", new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class), mediaPendingIntent); - mSession.setCallback(new MediaSessionCompat.Callback() { + mediaSession = new MediaSessionCompat(this, "Phonograph", new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class), getMediaButtonIntent()); + mediaSession.setCallback(new MediaSessionCompat.Callback() { @Override public void onPause() { pausePlaying(false); @@ -186,7 +186,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe stopPlaying(); } }); - mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | + MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); } private void registerEverything() { @@ -205,17 +206,10 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe return audioManager; } - private void initRemoteControlClient() { -// Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); -// mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); -// PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); -// remoteControlClient = new RemoteControlClient(mediaPendingIntent); -// remoteControlClient.setTransportControlFlags( -// RemoteControlClient.FLAG_KEY_MEDIA_PLAY | -// RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | -// RemoteControlClient.FLAG_KEY_MEDIA_NEXT | -// RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS); -// getAudioManager().registerRemoteControlClient(remoteControlClient); + private PendingIntent getMediaButtonIntent() { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); + return PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); } @Override @@ -292,7 +286,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe private void killEverythingAndReleaseResources() { stopPlaying(); - playingNotificationHelper.killNotification(); + stopForeground(true); savePosition(); saveQueues(); stopSelf(); @@ -306,8 +300,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe player.release(); player = null; } - mSession.setActive(false); - mSession.release(); + mediaSession.setActive(false); + mediaSession.release(); notifyChange(PLAYSTATE_CHANGED); } @@ -383,13 +377,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.setDataSource(getApplicationContext(), trackUri); player.prepareAsync(); - } catch (Exception e) { - player.reset(); - player = null; - notifyChange(PLAYSTATE_CHANGED); - - Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); - return; + } catch (Exception ignored) { + // handled in onError() } currentSongId = playingQueue.get(getPosition()).id; notifyChange(META_CHANGED); @@ -426,24 +415,26 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe final Song song = playingQueue.get(getPosition()); int playState = isPlaying() - ? PlaybackState.STATE_PLAYING - : PlaybackState.STATE_PAUSED; + ? PlaybackStateCompat.STATE_PLAYING + : PlaybackStateCompat.STATE_PAUSED; - if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) { - mSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(playState, getSongProgressMillis(), 1.0f).build()); + if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_IN_SONG_CHANGED)) { + MediaSessionHelper.applyState(mediaSession, new PlaybackStateCompat.Builder() + .setActions(getAvailablePlaybackStateActions()) + .setState(playState, player != null ? getSongProgressMillis() : 0, 1.0f).build()); } else if (what.equals(META_CHANGED)) { - mSession.setMetadata(new MediaMetadataCompat.Builder() - .putString(MediaMetadata.METADATA_KEY_ARTIST, song.artistName) - .putString(MediaMetadata.METADATA_KEY_ALBUM, song.albumName) - .putString(MediaMetadata.METADATA_KEY_TITLE, song.title) - .putLong(MediaMetadata.METADATA_KEY_DURATION, song.duration) - .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) - .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()) + mediaSession.setMetadata(new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()) .build()); - mSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(playState, getSongProgressMillis(), 1.0f).build()); + MediaSessionHelper.applyState(mediaSession, new PlaybackStateCompat.Builder() + .setActions(getAvailablePlaybackStateActions()) + .setState(playState, player != null ? getSongProgressMillis() : 0, 1.0f).build()); } currentAlbumArtUri = MusicUtil.getAlbumArtUri(song.albumId).toString(); @@ -474,14 +465,34 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe } private void updateMediaSessionBitmap(final Bitmap albumArt) { - MediaMetadataCompat current = mSession.getController().getMetadata(); + MediaMetadataCompat current = mediaSession.getController().getMetadata(); if (current == null) current = new MediaMetadataCompat.Builder().build(); - mSession.setMetadata(new MediaMetadataCompat.Builder(current) - .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt) + mediaSession.setMetadata(new MediaMetadataCompat.Builder(current) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt) .build()); } + private long getAvailablePlaybackStateActions() { + if (getPlayingQueue() == null || getPlayingQueue().isEmpty()) { + return 0; + } + + long actions = PlaybackStateCompat.ACTION_PLAY_PAUSE; + if (isPlaying()) { + actions |= PlaybackStateCompat.ACTION_PAUSE; + } else { + actions |= PlaybackStateCompat.ACTION_PLAY; + } + if (getPosition() > 0) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + } + if (getPosition() < getPlayingQueue().size() - 1) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; + } + return actions; + } + private void setUpMediaPlayerIfNeeded() { if (player == null) { player = new MediaPlayer(); @@ -496,7 +507,14 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe } private void updateNotification() { - playingNotificationHelper.buildNotification(playingQueue.get(position), isPlaying()); + final boolean isPlaying = isPlaying(); + Notification notification = PlayingNotificationHelper.buildNotification(this, mediaSession.getSessionToken(), getPlayingQueue().get(getPosition()), isPlaying); + if (isPlaying) + startForeground(NOTIFICATION_ID, notification); + else { + notificationManager.notify(NOTIFICATION_ID, notification); + stopForeground(false); + } } private void updateWidgets() { @@ -568,6 +586,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe player.reset(); player = null; notifyChange(PLAYSTATE_CHANGED); + Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); return false; } @@ -723,7 +742,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe } public void resumePlaying(boolean forceNoFading) { - mSession.setActive(true); + mediaSession.setActive(true); if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPauseAndInterruptions()) { playerHandler.removeMessages(FADEDOWNANDPAUSE); playerHandler.sendEmptyMessage(FADEUPANDRESUME); @@ -805,6 +824,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe public void seekTo(int millis) { player.seekTo(millis); + notifyChange(POSITION_IN_SONG_CHANGED); } public boolean isPlayerPrepared() { @@ -864,7 +884,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe private void notifyChange(final String what) { updateMediaSession(what); - if (what.equals(POSITION_CHANGED)) { + if (what.equals(POSITION_IN_SONG_CHANGED)) { return; } @@ -887,7 +907,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe if (what.equals(PLAYSTATE_CHANGED)) { final boolean isPlaying = isPlaying(); - playingNotificationHelper.updatePlayState(isPlaying); + updateNotification(); MusicPlayerWidget.updateWidgetsPlayState(this, isPlaying); } else if (what.equals(META_CHANGED)) { updateNotification(); @@ -962,7 +982,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe service.fadingDown = true; service.notifyChange(PLAYSTATE_CHANGED); } - currentPlayPauseFadeVolume -= .1f; + currentPlayPauseFadeVolume -= .2f; if (currentPlayPauseFadeVolume > 0f) { sendEmptyMessageDelayed(FADEDOWNANDPAUSE, 10); } else { @@ -980,7 +1000,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe service.notifyChange(PLAYSTATE_CHANGED); } service.resume(); - currentPlayPauseFadeVolume += .1f; + currentPlayPauseFadeVolume += .2f; if (currentPlayPauseFadeVolume < 1.0f) { sendEmptyMessageDelayed(FADEUPANDRESUME, 10); } else {