aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordaneren2005 <daneren2005@gmail.com>2012-08-31 14:45:21 -0700
committerdaneren2005 <daneren2005@gmail.com>2012-08-31 14:45:21 -0700
commitf9fcaefd307d0cd7dcd4401f5f264d3566166a03 (patch)
tree7f3d4e2e8e801e18cb5687089f0d751c05bea3b8
parent56578901b52e833368dfef149206b8a9a8d89f6f (diff)
parentd0e1ef7dca6fefa2815c1e633901cd369f938b63 (diff)
downloaddsub-f9fcaefd307d0cd7dcd4401f5f264d3566166a03.tar.gz
dsub-f9fcaefd307d0cd7dcd4401f5f264d3566166a03.tar.bz2
dsub-f9fcaefd307d0cd7dcd4401f5f264d3566166a03.zip
Merge pull request #14 from kurthardin/master
Add support for lock screen controls
-rw-r--r--.gitignore4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/domain/PlayerState.java28
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java81
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java51
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java58
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/Util.java43
7 files changed, 223 insertions, 48 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..d466c271
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+subsonic-android/.classpath
+subsonic-android/.project
+subsonic-android/bin/*
+subsonic-android/gen/* \ No newline at end of file
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.