diff options
author | Kurt Hardin <kurthardin.dev@gmail.com> | 2012-08-29 12:15:50 -0700 |
---|---|---|
committer | Kurt Hardin <kurthardin.dev@gmail.com> | 2012-08-29 12:15:50 -0700 |
commit | d0e1ef7dca6fefa2815c1e633901cd369f938b63 (patch) | |
tree | 7f3d4e2e8e801e18cb5687089f0d751c05bea3b8 /subsonic-android | |
parent | 05a5dbaeeddd8e08291a2aa88c12eaf30aa82966 (diff) | |
download | dsub-d0e1ef7dca6fefa2815c1e633901cd369f938b63.tar.gz dsub-d0e1ef7dca6fefa2815c1e633901cd369f938b63.tar.bz2 dsub-d0e1ef7dca6fefa2815c1e633901cd369f938b63.zip |
Added support for lock screen controls.
Diffstat (limited to 'subsonic-android')
6 files changed, 219 insertions, 48 deletions
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/PlayerState.java b/subsonic-android/src/github/daneren2005/dsub/domain/PlayerState.java index 6a4fd06c..2b63077b 100644 --- a/subsonic-android/src/github/daneren2005/dsub/domain/PlayerState.java +++ b/subsonic-android/src/github/daneren2005/dsub/domain/PlayerState.java @@ -18,17 +18,29 @@ */ package github.daneren2005.dsub.domain; +import android.media.RemoteControlClient; + /** * @author Sindre Mehus * @version $Id$ */ public enum PlayerState { - IDLE, - DOWNLOADING, - PREPARING, - PREPARED, - STARTED, - STOPPED, - PAUSED, - COMPLETED + IDLE(RemoteControlClient.PLAYSTATE_STOPPED), + DOWNLOADING(RemoteControlClient.PLAYSTATE_BUFFERING), + PREPARING(RemoteControlClient.PLAYSTATE_BUFFERING), + PREPARED(RemoteControlClient.PLAYSTATE_STOPPED), + STARTED(RemoteControlClient.PLAYSTATE_PLAYING), + STOPPED(RemoteControlClient.PLAYSTATE_STOPPED), + PAUSED(RemoteControlClient.PLAYSTATE_PAUSED), + COMPLETED(RemoteControlClient.PLAYSTATE_STOPPED); + + private final int mRemoteControlClientPlayState; + + private PlayerState(int playState) { + mRemoteControlClientPlayState = playState; + } + + public int getRemoteControlClientPlayState() { + return mRemoteControlClientPlayState; + } } diff --git a/subsonic-android/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java b/subsonic-android/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java index da4a4998..cdbe8c4d 100644 --- a/subsonic-android/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java +++ b/subsonic-android/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java @@ -40,11 +40,5 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver { Intent serviceIntent = new Intent(context, DownloadServiceImpl.class); serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); context.startService(serviceIntent); - - try { - abortBroadcast(); - } catch (Exception x) { - // Ignored. - } } } diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java index 8c2be5ee..bce1cca8 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java @@ -18,12 +18,18 @@ */ package github.daneren2005.dsub.service; +import android.annotation.TargetApi; +import android.app.PendingIntent; import android.app.Service; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioManager; +import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; +import android.media.RemoteControlClient; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; @@ -33,6 +39,7 @@ import github.daneren2005.dsub.audiofx.VisualizerController; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.domain.RepeatMode; +import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; import github.daneren2005.dsub.util.CancellableTask; import github.daneren2005.dsub.util.LRUCache; import github.daneren2005.dsub.util.ShufflePlayBuffer; @@ -63,6 +70,10 @@ public class DownloadServiceImpl extends Service implements DownloadService { public static final String CMD_PREVIOUS = "github.daneren2005.dsub.CMD_PREVIOUS"; public static final String CMD_NEXT = "github.daneren2005.dsub.CMD_NEXT"; + + private RemoteControlClient mRemoteControlClient; + private ImageLoader imageLoader; + private final IBinder binder = new SimpleServiceBinder<DownloadService>(this); private MediaPlayer mediaPlayer; private final List<DownloadFile> downloadList = new ArrayList<DownloadFile>(); @@ -110,9 +121,12 @@ public class DownloadServiceImpl extends Service implements DownloadService { } } - @Override + @TargetApi(14) + @Override public void onCreate() { super.onCreate(); + + imageLoader = new ImageLoader(this); mediaPlayer = new MediaPlayer(); mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK); @@ -124,6 +138,42 @@ public class DownloadServiceImpl extends Service implements DownloadService { return false; } }); + +// try { +// Class.forName("android.media.RemoteControlClient"); + if (Build.VERSION.SDK_INT >= 14) { + + Util.requestAudioFocus(this); + Util.registerMediaButtonEventReceiver(this); + + // Use the remote control APIs (if available) to set the playback state + if (mRemoteControlClient == null) { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + ComponentName mediaButtonReceiverComponent = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName()); +// audioManager.registerMediaButtonEventReceiver(mediaButtonReceiverComponent); + // build the PendingIntent for the remote control client + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiverComponent); + PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + // create and register the remote control client + mRemoteControlClient = new RemoteControlClient(mediaPendingIntent); + audioManager.registerRemoteControlClient(mRemoteControlClient); + } + + mRemoteControlClient.setPlaybackState( + RemoteControlClient.PLAYSTATE_STOPPED); + + mRemoteControlClient.setTransportControlFlags( + RemoteControlClient.FLAG_KEY_MEDIA_PLAY | + RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | + RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE | + RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS | + RemoteControlClient.FLAG_KEY_MEDIA_NEXT | + RemoteControlClient.FLAG_KEY_MEDIA_STOP); + } +// } catch (ClassNotFoundException x) { +// // Ignored. +// } if (equalizerAvailable) { equalizerController = new EqualizerController(this, mediaPlayer); @@ -395,10 +445,12 @@ public class DownloadServiceImpl extends Service implements DownloadService { } } - synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) { + @TargetApi(14) + synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) { this.currentPlaying = currentPlaying; if (currentPlaying != null) { + Util.requestAudioFocus(this); Util.broadcastNewTrackInfo(this, currentPlaying.getSong()); } else { Util.broadcastNewTrackInfo(this, null); @@ -409,6 +461,24 @@ public class DownloadServiceImpl extends Service implements DownloadService { } else { Util.hidePlayingNotification(this, this, handler); } + + if (mRemoteControlClient != null) { + MusicDirectory.Entry currentSong = currentPlaying == null ? null: currentPlaying.getSong(); + // Update the remote controls + mRemoteControlClient.editMetadata(true) + .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, currentSong == null ? null : currentSong.getArtist()) + .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, currentSong == null ? null : currentSong.getAlbum()) + .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, currentSong == null ? null : currentSong.getTitle()) + .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, currentSong == null ? 0 : currentSong.getDuration()) + .apply(); + if (currentSong == null) { + mRemoteControlClient.editMetadata(true) + .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, null) + .apply(); + } else { + imageLoader.loadImage(this, mRemoteControlClient, currentSong); + } + } } @Override @@ -618,7 +688,8 @@ public class DownloadServiceImpl extends Service implements DownloadService { return playerState; } - synchronized void setPlayerState(PlayerState playerState) { + @TargetApi(14) + synchronized void setPlayerState(PlayerState playerState) { Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")"); if (playerState == PAUSED) { @@ -630,6 +701,10 @@ public class DownloadServiceImpl extends Service implements DownloadService { Util.broadcastPlaybackStatusChange(this, playerState); this.playerState = playerState; + if (mRemoteControlClient != null) { + mRemoteControlClient.setPlaybackState(playerState.getRemoteControlClientPlayState()); + } + if (show) { Util.showPlayingNotification(this, this, handler, currentPlaying.getSong()); } else if (hide) { diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java index 7e06eb1e..564cced6 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java @@ -29,6 +29,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.media.RemoteControlClient; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; @@ -209,28 +210,34 @@ public class DownloadServiceLifecycleSupport { } switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_HEADSETHOOK: - downloadService.togglePlayPause(); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - downloadService.previous(); - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) { - downloadService.next(); - } - break; - case KeyEvent.KEYCODE_MEDIA_STOP: - downloadService.reset(); - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - downloadService.start(); - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - downloadService.pause(); - default: - break; + case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_HEADSETHOOK: + downloadService.togglePlayPause(); + break; + case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + downloadService.previous(); + break; + case RemoteControlClient.FLAG_KEY_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_NEXT: + if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) { + downloadService.next(); + } + break; + case RemoteControlClient.FLAG_KEY_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_STOP: + downloadService.reset(); + break; + case RemoteControlClient.FLAG_KEY_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PLAY: + downloadService.start(); + break; + case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + downloadService.pause(); + default: + break; } } diff --git a/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java b/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java index a3c36493..0c4bc215 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java @@ -18,6 +18,7 @@ */ package github.daneren2005.dsub.util; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -28,6 +29,7 @@ import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; +import android.media.RemoteControlClient; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; @@ -49,6 +51,7 @@ import java.util.concurrent.LinkedBlockingQueue; * * @author Sindre Mehus */ +@TargetApi(14) public class ImageLoader implements Runnable { private static final String TAG = ImageLoader.class.getSimpleName(); @@ -98,7 +101,23 @@ public class ImageLoader implements Runnable { if (!large) { setUnknownImage(view, large); } - queue.offer(new Task(view, entry, size, large, large, crossfade)); + queue.offer(new Task(view.getContext(), view, null, entry, size, large, large, crossfade)); + } + + public void loadImage(Context context, RemoteControlClient remoteControl, MusicDirectory.Entry entry) { + if (entry == null || entry.getCoverArt() == null) { + setUnknownImage(remoteControl); + return; + } + + Drawable drawable = cache.get(getKey(entry.getCoverArt(), imageSizeDefault)); + if (drawable != null) { + setImage(remoteControl, drawable); + return; + } + + setUnknownImage(remoteControl); + queue.offer(new Task(context, null, remoteControl, entry, imageSizeDefault, false, false, false)); } private String getKey(String coverArtId, int size) { @@ -130,6 +149,15 @@ public class ImageLoader implements Runnable { } } } + + private void setImage(RemoteControlClient remoteControl, Drawable drawable) { + Bitmap origBitmap = ((BitmapDrawable)drawable).getBitmap(); + remoteControl.editMetadata(false) + .putBitmap( + RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, + origBitmap.copy(origBitmap.getConfig(), true)) + .apply(); + } private void setUnknownImage(View view, boolean large) { if (large) { @@ -142,6 +170,10 @@ public class ImageLoader implements Runnable { } } } + + private void setUnknownImage(RemoteControlClient remoteControl) { + setImage(remoteControl, largeUnknownImage); + } public void clear() { queue.clear(); @@ -206,9 +238,11 @@ public class ImageLoader implements Runnable { return bitmapWithReflection; } - - private class Task { + + private class Task { + private final Context context; private final View view; + private final RemoteControlClient remoteControl; private final MusicDirectory.Entry entry; private final Handler handler; private final int size; @@ -216,8 +250,10 @@ public class ImageLoader implements Runnable { private final boolean saveToFile; private final boolean crossfade; - public Task(View view, MusicDirectory.Entry entry, int size, boolean reflection, boolean saveToFile, boolean crossfade) { - this.view = view; + public Task(Context context, View view, RemoteControlClient remoteControl, MusicDirectory.Entry entry, int size, boolean reflection, boolean saveToFile, boolean crossfade) { + this.context = context; + this.view = view; + this.remoteControl = remoteControl; this.entry = entry; this.size = size; this.reflection = reflection; @@ -228,20 +264,24 @@ public class ImageLoader implements Runnable { public void execute() { try { - MusicService musicService = MusicServiceFactory.getMusicService(view.getContext()); - Bitmap bitmap = musicService.getCoverArt(view.getContext(), entry, size, saveToFile, null); + MusicService musicService = MusicServiceFactory.getMusicService(context); + Bitmap bitmap = musicService.getCoverArt(context, entry, size, saveToFile, null); if (reflection) { bitmap = createReflection(bitmap); } - final Drawable drawable = Util.createDrawableFromBitmap(view.getContext(), bitmap); + final Drawable drawable = Util.createDrawableFromBitmap(context, bitmap); cache.put(getKey(entry.getCoverArt(), size), drawable); handler.post(new Runnable() { @Override public void run() { - setImage(view, drawable, crossfade); + if (view != null) { + setImage(view, drawable, crossfade); + } else if (remoteControl != null) { + setImage(remoteControl, drawable); + } } }); } catch (Throwable x) { diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index 53b3d225..37dfc6f5 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -18,6 +18,7 @@ */ package github.daneren2005.dsub.util; +import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.app.Notification; @@ -36,6 +37,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.Build; import android.os.Environment; import android.os.Handler; import android.util.Log; @@ -732,6 +734,47 @@ public final class Util { // Ignored. } } + + @TargetApi(8) + public static void requestAudioFocus(Context context) { +// // AudioManager.requestAudioFocus() was introduced in Android 2.2. +// // Use reflection to maintain compatibility with 1.5. +// try { +// AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); +// Class<?> onAudioFocusChangeListener = Class.forName("android.media.AudioManager.OnAudioFocuseChangeListener"); +// Method requestAudioFocus = AudioManager.class.getMethod( +// "requestAudioFocus", onAudioFocusChangeListener, Integer.TYPE, Integer.TYPE); +// requestAudioFocus.invoke(audioManager, null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); +//// } catch (Throwable x) { +//// // Ignored. +//// } +// } catch (ClassNotFoundException e) { +// Log.e(TAG, "ClassNotFoundException getting OnAudioFocusChangeListener."); +// } catch (NoSuchMethodException e) { +// Log.e(TAG, "NoSuchMethodException getting requestAudioFocus."); +// } catch (InvocationTargetException e){ +// // Unpack original exception when possible +// Throwable cause = e.getCause(); +// if (cause instanceof RuntimeException) { +// throw (RuntimeException) cause; +// } else if (cause instanceof Error) { +// throw (Error) cause; +// } else { +// // Unexpected checked exception; wrap and re-throw +// throw new RuntimeException(e); +// } +// } catch (IllegalArgumentException e) { +// Log.e(TAG, "IllegalArgumentException invoking requestAudioFocus."); +// e.printStackTrace(); +// } catch (IllegalAccessException e) { +// Log.e(TAG, "IllegalAccessException invoking requestAudioFocus."); +// e.printStackTrace(); +// } + if (Build.VERSION.SDK_INT >= 8) { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + } + } private static void startForeground(Service service, int notificationId, Notification notification) { // Service.startForeground() was introduced in Android 2.0. |