From 826d874758f47fd705d6e57beb47493ba719cd2d Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Fri, 17 Mar 2017 22:08:05 +0100 Subject: [PATCH] Completely switched to MediaSession --- app/src/main/AndroidManifest.xml | 6 - .../gramophone/service/MusicService.java | 201 ++++++++++-------- ...21.java => PlayingNotificationImpl24.java} | 60 +----- 3 files changed, 120 insertions(+), 147 deletions(-) rename app/src/main/java/com/kabouzeid/gramophone/service/notification/{PlayingNotificationImpl21.java => PlayingNotificationImpl24.java} (80%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 642ddad9..6e0cd7d5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,12 +98,6 @@ android:name=".service.MusicService" android:enabled="true" /> - - - - - - 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 387e6d55..28db5203 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -14,9 +14,8 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.media.AudioManager; -import android.media.MediaMetadataRetriever; -import android.media.RemoteControlClient; import android.media.audiofx.AudioEffect; +import android.media.session.MediaSession; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -30,6 +29,10 @@ import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaButtonReceiver; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; import android.widget.Toast; import com.bumptech.glide.BitmapRequestBuilder; @@ -50,7 +53,7 @@ import com.kabouzeid.gramophone.provider.MusicPlaybackQueueStore; import com.kabouzeid.gramophone.provider.SongPlayCountStore; import com.kabouzeid.gramophone.service.notification.PlayingNotification; import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl; -import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl21; +import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl24; import com.kabouzeid.gramophone.service.playback.Playback; import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; @@ -129,11 +132,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private int repeatMode; private boolean queuesRestored; private boolean pausedByTransientLossOfFocus; - private boolean receiversAndRemoteControlClientRegistered; private PlayingNotification playingNotification; private AudioManager audioManager; @SuppressWarnings("deprecation") - private RemoteControlClient remoteControlClient; + private MediaSessionCompat mediaSession; + private MediaButtonIntentReceiver mediaButtonIntentReceiver = new MediaButtonIntentReceiver(); private PowerManager.WakeLock wakeLock; private PlaybackHandler playerHandler; private final AudioManager.OnAudioFocusChangeListener audioFocusListener = new AudioManager.OnAudioFocusChangeListener() { @@ -147,6 +150,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private HandlerThread queueSaveHandlerThread; private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); private ThrottledSeekHandler throttledSeekHandler; + private boolean becomingNoisyReceiverRegistered; + private IntentFilter becomingNoisyReceiverIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, @NonNull Intent intent) { @@ -180,6 +185,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP playback = new MultiPlayer(this); playback.setCallbacks(this); + setupMediaSession(); + // queue saving needs to run on a separate thread so that it doesn't block the playback handler events queueSaveHandlerThread = new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); queueSaveHandlerThread.start(); @@ -187,12 +194,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP uiThreadHandler = new Handler(); - registerReceiversAndRemoteControlClient(); - registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - playingNotification = new PlayingNotificationImpl21(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + playingNotification = new PlayingNotificationImpl24(); } else { playingNotification = new PlayingNotificationImpl(); } @@ -209,16 +214,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP restoreState(); - sendBroadcast(new Intent("com.kabouzeid.gramophone.PHONOGRAPH_MUSIC_SERVICE_CREATED")); - } + mediaSession.setActive(true); - private void registerReceiversAndRemoteControlClient() { - if (!receiversAndRemoteControlClientRegistered) { - registerReceiver(becomingNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - getAudioManager().registerMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); - initRemoteControlClient(); - receiversAndRemoteControlClientRegistered = true; - } + sendBroadcast(new Intent("com.kabouzeid.gramophone.PHONOGRAPH_MUSIC_SERVICE_CREATED")); } private AudioManager getAudioManager() { @@ -228,21 +226,62 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return audioManager; } - @SuppressWarnings("deprecation") - private void initRemoteControlClient() { + private void setupMediaSession() { + ComponentName mediaButtonReceiverComponentName = new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); - remoteControlClient = new RemoteControlClient(PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0)); - 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); + mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); + + PendingIntent mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + + mediaSession = new MediaSessionCompat(this, "Phonograph", mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent); + mediaSession.setCallback(new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + play(); + } + + @Override + public void onPause() { + pause(); + } + + @Override + public void onSkipToNext() { + playNextSong(true); + } + + @Override + public void onSkipToPrevious() { + back(true); + } + + @Override + public void onStop() { + quit(); + } + + @Override + public void onSeekTo(long pos) { + seek((int) pos); + } + + @Override + public boolean onMediaButtonEvent(Intent mediaButtonEvent) { + mediaButtonIntentReceiver.onReceive(MusicService.this, mediaButtonEvent); + return true; + } + }); + + mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS + | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); + + mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); } @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + MediaButtonReceiver.handleIntent(mediaSession, intent); if (intent != null) { if (intent.getAction() != null) { restoreQueuesAndPositionIfNecessary(); @@ -268,8 +307,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP playNextSong(true); break; case ACTION_STOP: - stop(); - break; case ACTION_QUIT: return quit(); } @@ -282,6 +319,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onDestroy() { unregisterReceiver(widgetIntentReceiver); + if (becomingNoisyReceiverRegistered) { + unregisterReceiver(becomingNoisyReceiver); + becomingNoisyReceiverRegistered = false; + } + mediaSession.setActive(false); quit(); releaseResources(); getContentResolver().unregisterContentObserver(mediaStoreObserver); @@ -311,15 +353,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return true; } - private void unregisterReceiversAndRemoteControlClient() { - if (receiversAndRemoteControlClientRegistered) { - unregisterReceiver(becomingNoisyReceiver); - getAudioManager().unregisterRemoteControlClient(remoteControlClient); - getAudioManager().unregisterMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); - receiversAndRemoteControlClientRegistered = false; - } - } - private static final class QueueSaveHandler extends Handler { @NonNull private final WeakReference mService; @@ -399,7 +432,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } private int quit() { - unregisterReceiversAndRemoteControlClient(); pause(); playingNotification.stop(); @@ -407,6 +439,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return START_STICKY; } else { closeAudioEffectSession(); + getAudioManager().abandonAudioFocus(audioFocusListener); stopSelf(); return START_NOT_STICKY; } @@ -427,14 +460,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } playback.release(); playback = null; - } - - public void stop() { - pausedByTransientLossOfFocus = false; - savePositionInTrack(); - playback.stop(); - notifyChange(PLAY_STATE_CHANGED); - getAudioManager().abandonAudioFocus(audioFocusListener); + mediaSession.release(); } public boolean isPlaying() { @@ -499,31 +525,29 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return (getAudioManager().requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); } - private void updateRemoteControlClient() { - updateRemoteControlClient(PreferenceUtil.getInstance(this).albumArtOnLockscreen(), PreferenceUtil.getInstance(this).blurredAlbumArt()); - } - - private void updateRemoteControlClient(boolean showAlbumArt, final boolean blurAlbumArt) { + private void updateMediaSession() { final Song song = getCurrentSong(); - remoteControlClient - .editMetadata(!showAlbumArt) - .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title) - .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, song.albumName) - .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artistName) - .putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, song.artistName) + final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_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_YEAR, song.year) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); - .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, song.duration) - .putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, song.trackNumber) - // .putLong(MediaMetadataRetriever.METADATA_KEY_YEAR, song.year) // throws java.lang.IllegalArgumentException: Invalid type 'long' for key 8 on some Android versions + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); + } - .apply(); - if (showAlbumArt) { + if (PreferenceUtil.getInstance(this).albumArtOnLockscreen()) { final Point screenSize = Util.getScreenSize(MusicService.this); final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) .checkIgnoreMediaStore(MusicService.this) .asBitmap().build(); - if (blurAlbumArt) { + if (PreferenceUtil.getInstance(this).blurredAlbumArt()) { request.transform(new BlurTransformation.Builder(MusicService.this).build()); } runOnUiThread(new Runnable() { @@ -533,28 +557,21 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onLoadFailed(Exception e, Drawable errorDrawable) { super.onLoadFailed(e, errorDrawable); - updateRemoteControlClientBitmap(null); + mediaSession.setMetadata(metaData.build()); } @Override public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { - updateRemoteControlClientBitmap(copy(resource)); + metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); + mediaSession.setMetadata(metaData.build()); } }); } }); - } else { - updateRemoteControlClientBitmap(null); + mediaSession.setMetadata(metaData.build()); } - } - private void updateRemoteControlClientBitmap(final Bitmap albumArt) { - //noinspection deprecation - remoteControlClient - .editMetadata(false) - .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt) - .apply(); } private static Bitmap copy(Bitmap bitmap) { @@ -787,8 +804,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP if (!playback.isInitialized()) { playSongAt(getPosition()); } else { - registerReceiversAndRemoteControlClient(); playback.start(); + if (!becomingNoisyReceiverRegistered) { + registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); + becomingNoisyReceiverRegistered = true; + } if (notHandledMetaChangedForCurrentTrack) { handleChangeInternal(META_CHANGED); notHandledMetaChangedForCurrentTrack = false; @@ -957,13 +977,25 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP appWidgetSmall.notifyChange(this, what); } + private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_STOP + | PlaybackStateCompat.ACTION_SEEK_TO; + private void handleChangeInternal(@NonNull final String what) { switch (what) { case PLAY_STATE_CHANGED: final boolean isPlaying = isPlaying(); playingNotification.update(); - //noinspection deprecation - remoteControlClient.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED); + mediaSession.setPlaybackState( + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState(isPlaying ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getPosition(), 1) + .build()); if (!isPlaying && getSongProgressMillis() > 0) { savePositionInTrack(); } @@ -971,7 +1003,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP break; case META_CHANGED: playingNotification.update(); - updateRemoteControlClient(); + updateMediaSession(); savePosition(); savePositionInTrack(); final Song currentSong = getCurrentSong(); @@ -982,6 +1014,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP songPlayCountHelper.notifySongChanged(currentSong); break; case QUEUE_CHANGED: + updateMediaSession(); saveState(); if (playingQueue.size() > 0) { prepareNext(); @@ -996,6 +1029,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return playback.getAudioSessionId(); } + public MediaSessionCompat getMediaSession() { + return mediaSession; + } + public void releaseWakeLock() { if (wakeLock.isHeld()) { wakeLock.release(); @@ -1018,7 +1055,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP break; case PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN: case PreferenceUtil.BLURRED_ALBUM_ART: - updateRemoteControlClient(); + updateMediaSession(); break; case PreferenceUtil.COLORED_NOTIFICATION: playingNotification.update(); @@ -1128,7 +1165,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP case FOCUS_CHANGE: switch (msg.arg1) { case AudioManager.AUDIOFOCUS_GAIN: - service.registerReceiversAndRemoteControlClient(); if (!service.isPlaying() && service.pausedByTransientLossOfFocus) { service.play(); service.pausedByTransientLossOfFocus = false; @@ -1140,7 +1176,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP case AudioManager.AUDIOFOCUS_LOSS: // Lost focus for an unbounded amount of time: stop playback and release media playback service.pause(); - service.unregisterReceiversAndRemoteControlClient(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java similarity index 80% rename from app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java rename to app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java index 435e1ade..056a2eae 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java @@ -9,9 +9,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.Drawable; -import android.media.session.MediaSession; import android.os.Build; -import android.support.v4.media.session.MediaSessionCompat; import android.support.v7.app.NotificationCompat; import android.support.v7.graphics.Palette; import android.text.TextUtils; @@ -23,13 +21,10 @@ import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; import com.kabouzeid.gramophone.model.Song; -import com.kabouzeid.gramophone.service.MediaButtonIntentReceiver; import com.kabouzeid.gramophone.service.MusicService; import com.kabouzeid.gramophone.ui.activities.MainActivity; import com.kabouzeid.gramophone.util.PreferenceUtil; -import hugo.weaving.DebugLog; - import static android.content.Context.NOTIFICATION_SERVICE; import static com.kabouzeid.gramophone.service.MusicService.ACTION_REWIND; import static com.kabouzeid.gramophone.service.MusicService.ACTION_SKIP; @@ -39,13 +34,12 @@ import static com.kabouzeid.gramophone.service.MusicService.ACTION_TOGGLE_PAUSE; * @author Karim Abou Zeid (kabouzeid) */ -public class PlayingNotificationImpl21 implements PlayingNotification { +public class PlayingNotificationImpl24 implements PlayingNotification { private static final int NOTIFY_MODE_FOREGROUND = 1; private static final int NOTIFY_MODE_BACKGROUND = 0; private MusicService service; - private MediaSessionCompat mediaSession; private NotificationManager notificationManager; private int notifyMode = NOTIFY_MODE_BACKGROUND; @@ -56,53 +50,6 @@ public class PlayingNotificationImpl21 implements PlayingNotification { public synchronized void init(MusicService service) { this.service = service; notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE); - setupMediaSession(); - } - - @DebugLog - private void setupMediaSession() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mediaSession = new MediaSessionCompat(service, "Phonograph"); - mediaSession.setCallback(new MediaSessionCompat.Callback() { - @Override - public void onPlay() { - service.play(); - } - - @Override - public void onPause() { - service.pause(); - } - - @Override - public void onSkipToNext() { - service.playNextSong(true); - } - - @Override - public void onSkipToPrevious() { - service.back(true); - } - - @Override - public void onStop() { - service.stop(); - } - - @Override - public void onSeekTo(long pos) { - service.seek((int) pos); - } - }); - - PendingIntent pi = PendingIntent.getBroadcast(service, 0, - new Intent(service, MediaButtonIntentReceiver.class), - PendingIntent.FLAG_UPDATE_CURRENT); - mediaSession.setMediaButtonReceiver(pi); - - mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS - | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); - } } @Override @@ -174,7 +121,7 @@ public class PlayingNotificationImpl21 implements PlayingNotification { .addAction(nextAction); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setStyle(new NotificationCompat.MediaStyle().setMediaSession(mediaSession.getSessionToken()).setShowActionsInCompactView(0, 1, 2)) + builder.setStyle(new NotificationCompat.MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2)) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); if (PreferenceUtil.getInstance(service).coloredNotification()) builder.setColor(color); @@ -184,9 +131,7 @@ public class PlayingNotificationImpl21 implements PlayingNotification { return; // notification has been stopped before loading was finished updateNotifyModeAndPostNotification(builder.build()); } - }); - } }); } @@ -224,6 +169,5 @@ public class PlayingNotificationImpl21 implements PlayingNotification { public synchronized void stop() { stopped = true; service.stopForeground(true); - mediaSession.release(); } }