Use new MediaSession API on 5.x

This commit is contained in:
Adrian Ulrich 2015-12-20 16:37:56 +01:00
parent 0e425b9198
commit 81c6527e73
4 changed files with 300 additions and 109 deletions

View File

@ -337,6 +337,10 @@ public final class PlaybackService extends Service
* The SensorManager service.
*/
private SensorManager mSensorManager;
/**
* A remote control client implementation
*/
private RemoteControl.Client mRemoteControlClient;
SongTimeline mTimeline;
private Song mCurrentSong;
@ -495,7 +499,8 @@ public final class PlaybackService extends Service
getContentResolver().registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mObserver);
RemoteControl.registerRemote(this, mAudioManager);
mRemoteControlClient = new RemoteControl().getClient(this);
mRemoteControlClient.registerRemote(mAudioManager);
mLooper = thread.getLooper();
mHandler = new Handler(mLooper, this);
@ -614,6 +619,9 @@ public final class PlaybackService extends Service
if (mSensorManager != null)
mSensorManager.unregisterListener(this);
if (mRemoteControlClient != null)
mRemoteControlClient.unregisterRemote();
super.onDestroy();
}
@ -833,7 +841,7 @@ public final class PlaybackService extends Service
} else if (PrefKeys.MEDIA_BUTTON.equals(key) || PrefKeys.MEDIA_BUTTON_BEEP.equals(key)) {
MediaButtonReceiver.reloadPreference(this);
} else if (PrefKeys.COVER_ON_LOCKSCREEN.equals(key)) {
RemoteControl.reloadPreference();
mRemoteControlClient.reloadPreference();
} else if (PrefKeys.USE_IDLE_TIMEOUT.equals(key) || PrefKeys.IDLE_TIMEOUT.equals(key)) {
mIdleTimeout = settings.getBoolean(PrefKeys.USE_IDLE_TIMEOUT, PrefDefaults.USE_IDLE_TIMEOUT) ? settings.getInt(PrefKeys.IDLE_TIMEOUT, PrefDefaults.IDLE_TIMEOUT) : 0;
userActionTriggered();
@ -1055,7 +1063,7 @@ public final class PlaybackService extends Service
if (mReadaheadEnabled)
triggerReadAhead();
RemoteControl.updateRemote(this, mCurrentSong, mState, mForceNotificationVisible);
mRemoteControlClient.updateRemote(mCurrentSong, mState, mForceNotificationVisible);
if (mStockBroadcast)
stockMusicBroadcast();

View File

@ -1,129 +1,46 @@
/*
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
* Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.blinkenlights.android.vanilla;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.os.Build;
public class RemoteControl {
/**
* Used with updateRemote method.
*/
private static RemoteControlClient sRemote;
/**
* Whether the cover should be shown. 1 for yes, 0 for no, -1 for
* uninitialized.
*/
private static int sShowCover = -1;
/**
* Perform initialization required for RemoteControlClient.
*
* @param context A context to use.
* @param am The AudioManager service.
* Returns a RemoteControl.Client implementation
*/
public static void registerRemote(Context context, AudioManager am)
{
if (!MediaButtonReceiver.useHeadsetControls(context)) {
// RemoteControlClient requires MEDIA_BUTTON intent
return;
}
MediaButtonReceiver.registerMediaButton(context);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(new ComponentName(context.getPackageName(), MediaButtonReceiver.class.getName()));
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, 0);
RemoteControlClient remote = new RemoteControlClient(mediaPendingIntent);
int flags = RemoteControlClient.FLAG_KEY_MEDIA_NEXT
| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
remote.setTransportControlFlags(flags);
am.registerRemoteControlClient(remote);
sRemote = remote;
public RemoteControl.Client getClient(Context context) {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
new RemoteControlImplLp(context) :
new RemoteControlImplKitKat(context) // legacy implementation, kept until we drop 4.x support
);
}
/**
* Uninitializes our cached preferences, forcing a reload
* Interface definition of our RemoteControl API
*/
public static void reloadPreference()
{
sShowCover = -1;
}
/**
* Update the remote with new metadata.
* {@link #registerRemote(Context, AudioManager)} must have been called
* first.
*
* @param context A context to use.
* @param song The song containing the new metadata.
* @param state PlaybackService state, used to determine playback state.
* @param keepPaused whether or not to keep the remote updated in paused mode
*/
public static void updateRemote(Context context, Song song, int state, boolean keepPaused)
{
RemoteControlClient remote = sRemote;
if (remote == null)
return;
boolean isPlaying = ((state & PlaybackService.FLAG_PLAYING) != 0);
if (sShowCover == -1) {
SharedPreferences settings = PlaybackService.getSettings(context);
sShowCover = settings.getBoolean(PrefKeys.COVER_ON_LOCKSCREEN, PrefDefaults.COVER_ON_LOCKSCREEN) ? 1 : 0;
}
remote.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
RemoteControlClient.MetadataEditor editor = remote.editMetadata(true);
if (song != null) {
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artist);
editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, song.album);
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title);
Bitmap bitmap = song.getCover(context);
if (bitmap != null && sShowCover == 1 && (isPlaying || keepPaused)) {
// Create a copy of the cover art, since RemoteControlClient likes
// to recycle what we give it.
bitmap = bitmap.copy(Bitmap.Config.RGB_565, false);
} else {
// Some lockscreen implementations fail to clear the cover artwork
// if we send a null bitmap. We are creating a 16x16 transparent
// bitmap to work around this limitation.
bitmap = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
}
editor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap);
}
editor.apply();
public interface Client {
public void registerRemote(AudioManager am);
public void unregisterRemote();
public void reloadPreference();
public void updateRemote(Song song, int state, boolean keepPaused);
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
* Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package ch.blinkenlights.android.vanilla;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
public class RemoteControlImplKitKat implements RemoteControl.Client {
/**
* Context of this instance
*/
private Context mContext;
/**
* Used with updateRemote method.
*/
private RemoteControlClient mRemote;
/**
* Whether the cover should be shown. 1 for yes, 0 for no, -1 for
* uninitialized.
*/
private int mShowCover = -1;
/**
* Creates a new instance
*
* @param context The context to use
*/
public RemoteControlImplKitKat(Context context) {
mContext = context;
}
/**
* Perform initialization required for RemoteControlClient.
*
* @param am The AudioManager service.
*/
public void registerRemote(AudioManager am)
{
if (!MediaButtonReceiver.useHeadsetControls(mContext)) {
// RemoteControlClient requires MEDIA_BUTTON intent
return;
}
MediaButtonReceiver.registerMediaButton(mContext);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(new ComponentName(mContext.getPackageName(), MediaButtonReceiver.class.getName()));
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(mContext, 0, mediaButtonIntent, 0);
RemoteControlClient remote = new RemoteControlClient(mediaPendingIntent);
int flags = RemoteControlClient.FLAG_KEY_MEDIA_NEXT
| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
remote.setTransportControlFlags(flags);
am.registerRemoteControlClient(remote);
mRemote = remote;
}
public void unregisterRemote() {
// we should probably call am.unregisterRemoteControlClient but we never did and i'm not touching the legacy implementation.
}
/**
* Uninitializes our cached preferences, forcing a reload
*/
public void reloadPreference()
{
mShowCover = -1;
}
/**
* Update the remote with new metadata.
* {@link #registerRemote(Context, AudioManager)} must have been called
* first.
*
* @param song The song containing the new metadata.
* @param state PlaybackService state, used to determine playback state.
* @param keepPaused whether or not to keep the remote updated in paused mode
*/
public void updateRemote(Song song, int state, boolean keepPaused)
{
RemoteControlClient remote = mRemote;
if (remote == null)
return;
boolean isPlaying = ((state & PlaybackService.FLAG_PLAYING) != 0);
if (mShowCover == -1) {
SharedPreferences settings = PlaybackService.getSettings(mContext);
mShowCover = settings.getBoolean(PrefKeys.COVER_ON_LOCKSCREEN, PrefDefaults.COVER_ON_LOCKSCREEN) ? 1 : 0;
}
remote.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
RemoteControlClient.MetadataEditor editor = remote.editMetadata(true);
if (song != null) {
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artist);
editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, song.album);
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title);
Bitmap bitmap = song.getCover(mContext);
if (bitmap != null && mShowCover == 1 && (isPlaying || keepPaused)) {
// Create a copy of the cover art, since RemoteControlClient likes
// to recycle what we give it.
bitmap = bitmap.copy(Bitmap.Config.RGB_565, false);
} else {
// Some lockscreen implementations fail to clear the cover artwork
// if we send a null bitmap. We are creating a 16x16 transparent
// bitmap to work around this limitation.
bitmap = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
}
editor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap);
}
editor.apply();
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.blinkenlights.android.vanilla;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
public class RemoteControlImplLp implements RemoteControl.Client {
/**
* Context of this instance
*/
private Context mContext;
/**
* Objects MediaSession handle
*/
private MediaSession mMediaSession;
/**
* Whether the cover should be shown. 1 for yes, 0 for no, -1 for
* uninitialized.
*/
private int mShowCover = -1;
/**
* Creates a new instance
*
* @param context The context to use
*/
public RemoteControlImplLp(Context context) {
mContext = context;
}
/**
* Registers a new MediaSession on the device
*
* @param am The AudioManager service. (unused)
*/
public void registerRemote(AudioManager am) {
mMediaSession = new MediaSession(mContext, "VanillaMusic");
mMediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setActive(true);
}
/**
* Unregisters a registered media session
*/
public void unregisterRemote() {
mMediaSession.setActive(false);
mMediaSession.release();
}
/**
* Uninitializes our cached preferences, forcing a reload
*/
public void reloadPreference() {
mShowCover = -1;
}
/**
* Update the remote with new metadata.
* {@link #registerRemote(AudioManager)} must have been called
* first.
*
* @param song The song containing the new metadata.
* @param state PlaybackService state, used to determine playback state.
* @param keepPaused whether or not to keep the remote updated in paused mode
*/
public void updateRemote(Song song, int state, boolean keepPaused) {
MediaSession session = mMediaSession;
if (session == null)
return;
boolean isPlaying = ((state & PlaybackService.FLAG_PLAYING) != 0);
if (mShowCover == -1) {
SharedPreferences settings = PlaybackService.getSettings(mContext);
mShowCover = settings.getBoolean(PrefKeys.COVER_ON_LOCKSCREEN, PrefDefaults.COVER_ON_LOCKSCREEN) ? 1 : 0;
}
if (song != null) {
Bitmap bitmap = null;
if (mShowCover == 1 && (isPlaying || keepPaused)) {
bitmap = song.getCover(mContext);
if (bitmap != null)
bitmap.copy(Bitmap.Config.ARGB_8888, false);
}
session.setMetadata(new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_ARTIST, song.artist)
.putString(MediaMetadata.METADATA_KEY_ALBUM, song.album)
.putString(MediaMetadata.METADATA_KEY_TITLE, song.title)
.putLong(MediaMetadata.METADATA_KEY_DURATION, song.duration)
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)
.build());
}
int playbackState = (isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED);
session.setPlaybackState(new PlaybackState.Builder()
.setState(playbackState, PlaybackState.PLAYBACK_POSITION_UNKNOWN , 1.0f).build());
}
}