package com.android.music;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.media.AudioManager;
-import android.media.MediaFile;
+import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
import android.net.Uri;
-import android.os.Environment;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.widget.RemoteViews;
import android.widget.Toast;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.Random;
import java.util.Vector;
private static final int RELEASE_WAKELOCK = 2;
private static final int SERVER_DIED = 3;
private static final int FADEIN = 4;
- private static final int MAX_HISTORY_SIZE = 10;
+ private static final int MAX_HISTORY_SIZE = 100;
private MultiPlayer mPlayer;
private String mFileToPlay;
private int mShuffleMode = SHUFFLE_NONE;
private int mRepeatMode = REPEAT_NONE;
private int mMediaMountedCount = 0;
- private int [] mAutoShuffleList = null;
+ private long [] mAutoShuffleList = null;
private boolean mOneShot;
- private int [] mPlayList = null;
+ private long [] mPlayList = null;
private int mPlayListLen = 0;
private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
private Cursor mCursor;
private boolean mResumeAfterCall = false;
private boolean mIsSupposedToBePlaying = false;
private boolean mQuietMode = false;
-
+ private AudioManager mAudioManager;
+ // used to track what type of audio focus loss caused the playback to pause
+ private boolean mPausedByTransientLossOfFocus = false;
+
private SharedPreferences mPreferences;
// We use this to distinguish between different cards when saving/restoring playlists.
// This will have to change if we want to support multiple simultaneous cards.
float mCurrentVolume = 1.0f;
@Override
public void handleMessage(Message msg) {
+ MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
switch (msg.what) {
case FADEIN:
if (!isPlaying()) {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String cmd = intent.getStringExtra("command");
+ MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
next(true);
} else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
}
};
+ private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
+ public void onAudioFocusChanged(int focusChange) {
+ // AudioFocus is a new feature: focus updates are made verbose on purpose
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_LOSS:
+ Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
+ if(isPlaying()) {
+ mPausedByTransientLossOfFocus = false;
+ pause();
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
+ if(isPlaying()) {
+ mPausedByTransientLossOfFocus = true;
+ pause();
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN:
+ Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
+ if(!isPlaying() && mPausedByTransientLossOfFocus) {
+ mPausedByTransientLossOfFocus = false;
+ play();
+ }
+ break;
+ default:
+ Log.e(LOGTAG, "Unknown audio focus change code");
+ }
+ }
+ };
+
public MediaPlaybackService() {
}
@Override
public void onCreate() {
super.onCreate();
+
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mAudioManager.registerAudioFocusListener(mAudioFocusListener);
+ mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
+ MediaButtonIntentReceiver.class.getName()));
mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
- mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
+ mCardId = MusicUtils.getCardId(this);
registerExternalStorageListener();
mPlayer = new MultiPlayer();
mPlayer.setHandler(mMediaplayerHandler);
- // Clear leftover notification in case this service previously got killed while playing
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(PLAYBACKSERVICE_STATUS);
-
reloadQueue();
IntentFilter commandFilter = new IntentFilter();
public void onDestroy() {
// Check that we're not being destroyed while something is still playing.
if (isPlaying()) {
- Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
+ Log.e(LOGTAG, "Service being destroyed while still playing.");
}
// release all MediaPlayer resources, including the native player and wakelocks
mPlayer.release();
mPlayer = null;
+
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mAudioManager.unregisterAudioFocusListener(mAudioFocusListener);
// make sure there aren't any other messages coming
mDelayedStopHandler.removeCallbacksAndMessages(null);
// on the phone)
int len = mPlayListLen;
for (int i = 0; i < len; i++) {
- int n = mPlayList[i];
+ long n = mPlayList[i];
if (n == 0) {
q.append("0;");
} else {
while (n != 0) {
- int digit = n & 0xf;
+ int digit = (int)(n & 0xf);
n >>= 4;
q.append(hexdigits[digit]);
}
//Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
ed.putString("queue", q.toString());
ed.putInt("cardid", mCardId);
+ if (mShuffleMode != SHUFFLE_NONE) {
+ // In shuffle mode we need to save the history too
+ len = mHistory.size();
+ q.setLength(0);
+ for (int i = 0; i < len; i++) {
+ int n = mHistory.get(i);
+ if (n == 0) {
+ q.append("0;");
+ } else {
+ while (n != 0) {
+ int digit = (n & 0xf);
+ n >>= 4;
+ q.append(hexdigits[digit]);
+ }
+ q.append(";");
+ }
+ }
+ ed.putString("history", q.toString());
+ }
}
ed.putInt("curpos", mPlayPos);
if (mPlayer.isInitialized()) {
// To deal with this, try querying for the current file, and if
// that fails, wait a while and try again. If that too fails,
// assume there is a problem and don't restore the state.
- Cursor c = MusicUtils.query(this,
+ Cursor crsr = MusicUtils.query(this,
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
- if (c == null || c.getCount() == 0) {
+ if (crsr == null || crsr.getCount() == 0) {
// wait a bit and try again
SystemClock.sleep(3000);
- c = getContentResolver().query(
+ crsr = getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
}
- if (c != null) {
- c.close();
+ if (crsr != null) {
+ crsr.close();
}
// Make sure we don't auto-skip to the next song, since that
long seekpos = mPreferences.getLong("seekpos", 0);
seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
+ Log.d(LOGTAG, "restored queue, currently at position "
+ + position() + "/" + duration()
+ + " (requested " + seekpos + ")");
int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
shufmode = SHUFFLE_NONE;
}
+ if (shufmode != SHUFFLE_NONE) {
+ // in shuffle mode we need to restore the history too
+ q = mPreferences.getString("history", "");
+ qlen = q != null ? q.length() : 0;
+ if (qlen > 1) {
+ plen = 0;
+ n = 0;
+ shift = 0;
+ mHistory.clear();
+ for (int i = 0; i < qlen; i++) {
+ char c = q.charAt(i);
+ if (c == ';') {
+ if (n >= mPlayListLen) {
+ // bogus history data
+ mHistory.clear();
+ break;
+ }
+ mHistory.add(n);
+ n = 0;
+ shift = 0;
+ } else {
+ if (c >= '0' && c <= '9') {
+ n += ((c - '0') << shift);
+ } else if (c >= 'a' && c <= 'f') {
+ n += ((10 + c - 'a') << shift);
+ } else {
+ // bogus history data
+ mHistory.clear();
+ break;
+ }
+ shift += 4;
+ }
+ }
+ }
+ }
if (shufmode == SHUFFLE_AUTO) {
if (! makeAutoShuffleList()) {
shufmode = SHUFFLE_NONE;
}
@Override
- public void onStart(Intent intent, int startId) {
+ public int onStartCommand(Intent intent, int flags, int startId) {
mServiceStartId = startId;
mDelayedStopHandler.removeCallbacksAndMessages(null);
-
- String action = intent.getAction();
- String cmd = intent.getStringExtra("command");
-
- if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
- next(true);
- } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
- prev();
- } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
- if (isPlaying()) {
+
+ if (intent != null) {
+ String action = intent.getAction();
+ String cmd = intent.getStringExtra("command");
+ MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
+
+ if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
+ next(true);
+ } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
+ if (position() < 2000) {
+ prev();
+ } else {
+ seek(0);
+ play();
+ }
+ } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
+ if (isPlaying()) {
+ pause();
+ } else {
+ play();
+ }
+ } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
pause();
- } else {
- play();
+ } else if (CMDSTOP.equals(cmd)) {
+ pause();
+ seek(0);
}
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- seek(0);
}
// make sure the service will shut down on its own if it was
mDelayedStopHandler.removeCallbacksAndMessages(null);
Message msg = mDelayedStopHandler.obtainMessage();
mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ return START_STICKY;
}
@Override
closeExternalStorageFiles(intent.getData().getPath());
} else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
mMediaMountedCount++;
- mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
+ mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
reloadQueue();
notifyChange(QUEUE_CHANGED);
notifyChange(META_CHANGED);
private void notifyChange(String what) {
Intent i = new Intent(what);
- i.putExtra("id", Integer.valueOf(getAudioId()));
+ i.putExtra("id", Long.valueOf(getAudioId()));
i.putExtra("artist", getArtistName());
i.putExtra("album",getAlbumName());
i.putExtra("track", getTrackName());
// reallocate at 2x requested size so we don't
// need to grow and copy the array for every
// insert
- int [] newlist = new int[size * 2];
+ long [] newlist = new long[size * 2];
int len = mPlayList != null ? mPlayList.length : mPlayListLen;
for (int i = 0; i < len; i++) {
newlist[i] = mPlayList[i];
}
// insert the list of songs at the specified position in the playlist
- private void addToPlayList(int [] list, int position) {
+ private void addToPlayList(long [] list, int position) {
int addlen = list.length;
if (position < 0) { // overwrite
mPlayListLen = 0;
* @param list The list of tracks to append.
* @param action NOW, NEXT or LAST
*/
- public void enqueue(int [] list, int action) {
+ public void enqueue(long [] list, int action) {
synchronized(this) {
if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
addToPlayList(list, mPlayPos + 1);
* specified position is 0.
* @param list The new list of tracks.
*/
- public void open(int [] list, int position) {
+ public void open(long [] list, int position) {
synchronized (this) {
if (mShuffleMode == SHUFFLE_AUTO) {
mShuffleMode = SHUFFLE_NORMAL;
}
- int oldId = getAudioId();
+ long oldId = getAudioId();
int listlength = list.length;
boolean newlist = true;
if (mPlayListLen == listlength) {
index2 = mPlayListLen - 1;
}
if (index1 < index2) {
- int tmp = mPlayList[index1];
+ long tmp = mPlayList[index1];
for (int i = index1; i < index2; i++) {
mPlayList[i] = mPlayList[i+1];
}
mPlayPos--;
}
} else if (index2 < index1) {
- int tmp = mPlayList[index1];
+ long tmp = mPlayList[index1];
for (int i = index1; i > index2; i--) {
mPlayList[i] = mPlayList[i-1];
}
* Returns the current play list
* @return An array of integers containing the IDs of the tracks in the play list
*/
- public int [] getQueue() {
+ public long [] getQueue() {
synchronized (this) {
int len = mPlayListLen;
- int [] list = new int[len];
+ long [] list = new long[len];
for (int i = 0; i < len; i++) {
list[i] = mPlayList[i];
}
mCursor.moveToNext();
ensurePlayListCapacity(1);
mPlayListLen = 1;
- mPlayList[0] = mCursor.getInt(IDCOLIDX);
+ mPlayList[0] = mCursor.getLong(IDCOLIDX);
mPlayPos = 0;
}
}
if (!mQuietMode) {
Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
}
+ Log.d(LOGTAG, "Failed to open file for playback");
}
} else {
mOpenFailedCounter = 0;
* Starts playback of a previously opened file.
*/
public void play() {
+ mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
+ MediaButtonIntentReceiver.class.getName()));
+
if (mPlayer.isInitialized()) {
// if we are at the end of the song, go to the next song first
long duration = mPlayer.duration();
}
mPlayer.start();
- setForeground(true);
- NotificationManager nm = (NotificationManager)
- getSystemService(Context.NOTIFICATION_SERVICE);
-
RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
if (getAudioId() < 0) {
} else {
String artist = getArtistName();
views.setTextViewText(R.id.trackname, getTrackName());
- if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
+ if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
artist = getString(R.string.unknown_artist_name);
}
String album = getAlbumName();
- if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
+ if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
album = getString(R.string.unknown_album_name);
}
status.flags |= Notification.FLAG_ONGOING_EVENT;
status.icon = R.drawable.stat_notify_musicplayer;
status.contentIntent = PendingIntent.getActivity(this, 0,
- new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
- nm.notify(PLAYBACKSERVICE_STATUS, status);
+ new Intent("com.android.music.PLAYBACK_VIEWER")
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
+ startForeground(PLAYBACKSERVICE_STATUS, status);
if (!mIsSupposedToBePlaying) {
+ mIsSupposedToBePlaying = true;
notifyChange(PLAYSTATE_CHANGED);
}
- mIsSupposedToBePlaying = true;
+
} else if (mPlayListLen <= 0) {
// This is mostly so that if you press 'play' on a bluetooth headset
// without every having played anything before, it will still play
}
if (remove_status_icon) {
gotoIdleState();
+ } else {
+ stopForeground(false);
}
- setForeground(false);
if (remove_status_icon) {
mIsSupposedToBePlaying = false;
}
if (isPlaying()) {
mPlayer.pause();
gotoIdleState();
- setForeground(false);
mIsSupposedToBePlaying = false;
notifyChange(PLAYSTATE_CHANGED);
saveBookmarkIfNeeded();
}
if (mPlayListLen <= 0) {
+ Log.d(LOGTAG, "No play queue");
return;
}
} else {
// all done
gotoIdleState();
+ if (mIsSupposedToBePlaying) {
+ mIsSupposedToBePlaying = false;
+ notifyChange(PLAYSTATE_CHANGED);
+ }
return;
}
}
}
private void gotoIdleState() {
- NotificationManager nm =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(PLAYBACKSERVICE_STATUS);
mDelayedStopHandler.removeCallbacksAndMessages(null);
Message msg = mDelayedStopHandler.obtainMessage();
mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ stopForeground(true);
}
private void saveBookmarkIfNeeded() {
for (int i = 0; i < to_add; i++) {
// pick something at random from the list
int idx = mRand.nextInt(mAutoShuffleList.length);
- Integer which = mAutoShuffleList[idx];
+ long which = mAutoShuffleList[idx];
ensurePlayListCapacity(mPlayListLen + 1);
mPlayList[mPlayListLen++] = which;
notify = true;
return false;
}
int len = c.getCount();
- int[] list = new int[len];
+ long [] list = new long[len];
for (int i = 0; i < len; i++) {
c.moveToNext();
- list[i] = c.getInt(0);
+ list[i] = c.getLong(0);
}
mAutoShuffleList = list;
return true;
* @param id The id to be removed
* @return how many instances of the track were removed
*/
- public int removeTrack(int id) {
+ public int removeTrack(long id) {
int numremoved = 0;
synchronized (this) {
for (int i = 0; i < mPlayListLen; i++) {
* Returns the rowid of the currently playing file, or -1 if
* no file is currently playing.
*/
- public int getAudioId() {
+ public long getAudioId() {
synchronized (this) {
if (mPlayPos >= 0 && mPlayer.isInitialized()) {
return mPlayList[mPlayPos];
openCurrent();
play();
notifyChange(META_CHANGED);
+ if (mShuffleMode == SHUFFLE_AUTO) {
+ doAutoShuffleUpdate();
+ }
}
}
}
}
- public int getArtistId() {
+ public long getArtistId() {
synchronized (this) {
if (mCursor == null) {
return -1;
}
- return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
+ return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
}
}
}
}
- public int getAlbumId() {
+ public long getAlbumId() {
synchronized (this) {
if (mCursor == null) {
return -1;
}
- return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
+ return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
}
}
}
public void start() {
+ MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
mMediaPlayer.start();
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
return true;
default:
+ Log.d("MultiPlayer", "Error: " + what + "," + extra);
break;
}
return false;
{
mService.get().open(path, oneShot);
}
- public void open(int [] list, int position) {
+ public void open(long [] list, int position) {
mService.get().open(list, position);
}
public int getQueuePosition() {
public String getAlbumName() {
return mService.get().getAlbumName();
}
- public int getAlbumId() {
+ public long getAlbumId() {
return mService.get().getAlbumId();
}
public String getArtistName() {
return mService.get().getArtistName();
}
- public int getArtistId() {
+ public long getArtistId() {
return mService.get().getArtistId();
}
- public void enqueue(int [] list , int action) {
+ public void enqueue(long [] list , int action) {
mService.get().enqueue(list, action);
}
- public int [] getQueue() {
+ public long [] getQueue() {
return mService.get().getQueue();
}
public void moveQueueItem(int from, int to) {
public String getPath() {
return mService.get().getPath();
}
- public int getAudioId() {
+ public long getAudioId() {
return mService.get().getAudioId();
}
public long position() {
public int removeTracks(int first, int last) {
return mService.get().removeTracks(first, last);
}
- public int removeTrack(int id) {
+ public int removeTrack(long id) {
return mService.get().removeTrack(id);
}
public void setRepeatMode(int repeatmode) {
}
}
-
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
+ writer.println("Currently loaded:");
+ writer.println(getArtistName());
+ writer.println(getAlbumName());
+ writer.println(getTrackName());
+ writer.println(getPath());
+ writer.println("playing: " + mIsSupposedToBePlaying);
+ writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
+ writer.println("shuffle mode: " + mShuffleMode);
+ MusicUtils.debugDump(writer);
+ }
+
private final IBinder mBinder = new ServiceStub(this);
}