OSDN Git Service

Update the Music app to use the remote control focus API.
[android-x86/packages-apps-Music.git] / src / com / android / music / MediaPlaybackService.java
index 9d926f2..2413092 100644 (file)
 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;
@@ -33,11 +33,9 @@ import android.content.SharedPreferences.Editor;
 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;
@@ -45,13 +43,16 @@ import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.PowerManager.WakeLock;
 import android.provider.MediaStore;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.widget.RemoteViews;
 import android.widget.Toast;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneStateIntentReceiver;
 
+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;
 
@@ -96,22 +97,20 @@ public class MediaPlaybackService extends Service {
     public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
     public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
 
-    private static final int PHONE_CHANGED = 1;
     private static final int TRACK_ENDED = 1;
     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 PhoneStateIntentReceiver mPsir;
     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;
@@ -141,7 +140,10 @@ public class MediaPlaybackService extends Service {
     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.
@@ -152,35 +154,28 @@ public class MediaPlaybackService extends Service {
     // interval after which we stop the service when idle
     private static final int IDLE_DELAY = 60000; 
 
-    private Handler mPhoneHandler = new Handler() {
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case PHONE_CHANGED:
-                    Phone.State state = mPsir.getPhoneState();
-                    if (state == Phone.State.RINGING) {
-                        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-                        int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
-                        if (ringvolume > 0) {
-                            mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
-                            pause();
-                        }
-                    } else if (state == Phone.State.OFFHOOK) {
-                        // pause the music while a conversation is in progress
-                        mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
-                        pause();
-                    } else if (state == Phone.State.IDLE) {
-                        // start playing again
-                        if (mResumeAfterCall) {
-                            // resume playback only if music was playing
-                            // when the call was answered
-                            startAndFadeIn();
-                            mResumeAfterCall = false;
-                        }
-                    }
-                    break;
-                default:
-                    break;
+        public void onCallStateChanged(int state, String incomingNumber) {
+            if (state == TelephonyManager.CALL_STATE_RINGING) {
+                AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+                int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
+                if (ringvolume > 0) {
+                    mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
+                    pause();
+                }
+            } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+                // pause the music while a conversation is in progress
+                mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
+                pause();
+            } else if (state == TelephonyManager.CALL_STATE_IDLE) {
+                // start playing again
+                if (mResumeAfterCall) {
+                    // resume playback only if music was playing
+                    // when the call was answered
+                    startAndFadeIn();
+                    mResumeAfterCall = false;
+                }
             }
         }
     };
@@ -193,6 +188,7 @@ public class MediaPlaybackService extends Service {
         float mCurrentVolume = 1.0f;
         @Override
         public void handleMessage(Message msg) {
+            MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
             switch (msg.what) {
                 case FADEIN:
                     if (!isPlaying()) {
@@ -246,6 +242,7 @@ public class MediaPlaybackService extends Service {
         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)) {
@@ -270,17 +267,52 @@ public class MediaPlaybackService extends Service {
         }
     };
 
+    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() {
-        mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
-        mPsir.notifyPhoneCallState(PHONE_CHANGED);
     }
 
     @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();
 
@@ -288,10 +320,6 @@ public class MediaPlaybackService extends Service {
         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();
@@ -302,7 +330,8 @@ public class MediaPlaybackService extends Service {
         commandFilter.addAction(PREVIOUS_ACTION);
         registerReceiver(mIntentReceiver, commandFilter);
         
-        mPsir.registerIntent();
+        TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+        tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
         PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
         mWakeLock.setReferenceCounted(false);
@@ -317,23 +346,32 @@ public class MediaPlaybackService extends Service {
     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.");
         }
-        // and for good measure, call mPlayer.stop(), which calls MediaPlayer.reset(), which
-        // releases the MediaPlayer's wake lock, if any.
-        mPlayer.stop();
+        // 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);
+        mMediaplayerHandler.removeCallbacksAndMessages(null);
+
         if (mCursor != null) {
             mCursor.close();
             mCursor = null;
         }
 
+        TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+        tmgr.listen(mPhoneStateListener, 0);
+
         unregisterReceiver(mIntentReceiver);
         if (mUnmountReceiver != null) {
             unregisterReceiver(mUnmountReceiver);
             mUnmountReceiver = null;
         }
-        mPsir.unregisterIntent();
         mWakeLock.release();
         super.onDestroy();
     }
@@ -362,12 +400,12 @@ public class MediaPlaybackService extends Service {
             // 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]);
                     }
@@ -377,6 +415,25 @@ public class MediaPlaybackService extends Service {
             //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()) {
@@ -403,37 +460,37 @@ public class MediaPlaybackService extends Service {
             // the same one as when the playlist was saved
             q = mPreferences.getString("queue", "");
         }
-        if (q != null && q.length() > 1) {
+        int qlen = q != null ? q.length() : 0;
+        if (qlen > 1) {
             //Log.i("@@@@ service", "loaded queue: " + q);
-            String [] entries = q.split(";");
-            int len = entries.length;
-            ensurePlayListCapacity(len);
-            for (int i = 0; i < len; i++) {
-                if (newstyle) {
-                    String revhex = entries[i];
-                    int n = 0;
-                    for (int j = revhex.length() - 1; j >= 0 ; j--) {
-                        n <<= 4;
-                        char c = revhex.charAt(j);
-                        if (c >= '0' && c <= '9') {
-                            n += (c - '0');
-                        } else if (c >= 'a' && c <= 'f') {
-                            n += (10 + c - 'a');
-                        } else {
-                            // bogus playlist data
-                            len = 0;
-                            break;
-                        }
-                    }
-                    mPlayList[i] = n;
+            int plen = 0;
+            int n = 0;
+            int shift = 0;
+            for (int i = 0; i < qlen; i++) {
+                char c = q.charAt(i);
+                if (c == ';') {
+                    ensurePlayListCapacity(plen + 1);
+                    mPlayList[plen] = n;
+                    plen++;
+                    n = 0;
+                    shift = 0;
                 } else {
-                    mPlayList[i] = Integer.parseInt(entries[i]);
+                    if (c >= '0' && c <= '9') {
+                        n += ((c - '0') << shift);
+                    } else if (c >= 'a' && c <= 'f') {
+                        n += ((10 + c - 'a') << shift);
+                    } else {
+                        // bogus playlist data
+                        plen = 0;
+                        break;
+                    }
+                    shift += 4;
                 }
             }
-            mPlayListLen = len;
+            mPlayListLen = plen;
 
             int pos = mPreferences.getInt("curpos", 0);
-            if (pos < 0 || pos >= len) {
+            if (pos < 0 || pos >= mPlayListLen) {
                 // The saved playlist is bogus, discard it
                 mPlayListLen = 0;
                 return;
@@ -445,18 +502,18 @@ public class MediaPlaybackService extends Service {
             // 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
@@ -481,6 +538,9 @@ public class MediaPlaybackService extends Service {
             
             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) {
@@ -492,6 +552,41 @@ public class MediaPlaybackService extends Service {
             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;
@@ -515,28 +610,36 @@ public class MediaPlaybackService extends Service {
     }
 
     @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
@@ -544,6 +647,7 @@ public class MediaPlaybackService extends Service {
         mDelayedStopHandler.removeCallbacksAndMessages(null);
         Message msg = mDelayedStopHandler.obtainMessage();
         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+        return START_STICKY;
     }
     
     @Override
@@ -620,7 +724,7 @@ public class MediaPlaybackService extends Service {
                         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);
@@ -657,7 +761,7 @@ public class MediaPlaybackService extends Service {
     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());
@@ -678,8 +782,8 @@ public class MediaPlaybackService extends Service {
             // 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];
-            int len = mPlayListLen;
+            long [] newlist = new long[size * 2];
+            int len = mPlayList != null ? mPlayList.length : mPlayListLen;
             for (int i = 0; i < len; i++) {
                 newlist[i] = mPlayList[i];
             }
@@ -690,7 +794,7 @@ public class MediaPlaybackService extends Service {
     }
     
     // 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;
@@ -723,7 +827,7 @@ public class MediaPlaybackService extends Service {
      * @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);
@@ -756,12 +860,12 @@ public class MediaPlaybackService extends Service {
      * 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) {
@@ -808,7 +912,7 @@ public class MediaPlaybackService extends Service {
                 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];
                 }
@@ -819,7 +923,7 @@ public class MediaPlaybackService extends Service {
                         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];
                 }
@@ -838,10 +942,10 @@ public class MediaPlaybackService extends Service {
      * 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];
             }
@@ -944,7 +1048,7 @@ public class MediaPlaybackService extends Service {
                             mCursor.moveToNext();
                             ensurePlayListCapacity(1);
                             mPlayListLen = 1;
-                            mPlayList[0] = mCursor.getInt(IDCOLIDX);
+                            mPlayList[0] = mCursor.getLong(IDCOLIDX);
                             mPlayPos = 0;
                         }
                     }
@@ -966,6 +1070,7 @@ public class MediaPlaybackService extends Service {
                     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;
@@ -977,19 +1082,21 @@ public class MediaPlaybackService extends Service {
      * 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
-            if (mRepeatMode != REPEAT_CURRENT &&
-                mPlayer.position() >= mPlayer.duration() - 1) {
+            long duration = mPlayer.duration();
+            if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
+                mPlayer.position() >= duration - 2000) {
                 next(true);
             }
 
             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) {
@@ -999,11 +1106,11 @@ public class MediaPlaybackService extends Service {
             } 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);
                 }
                 
@@ -1012,19 +1119,19 @@ public class MediaPlaybackService extends Service {
                         );
             }
             
-            Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
-            statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
             Notification status = new Notification();
             status.contentView = views;
             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
@@ -1044,8 +1151,9 @@ public class MediaPlaybackService extends Service {
         }
         if (remove_status_icon) {
             gotoIdleState();
+        } else {
+            stopForeground(false);
         }
-        setForeground(false);
         if (remove_status_icon) {
             mIsSupposedToBePlaying = false;
         }
@@ -1066,7 +1174,6 @@ public class MediaPlaybackService extends Service {
             if (isPlaying()) {
                 mPlayer.pause();
                 gotoIdleState();
-                setForeground(false);
                 mIsSupposedToBePlaying = false;
                 notifyChange(PLAYSTATE_CHANGED);
                 saveBookmarkIfNeeded();
@@ -1148,6 +1255,11 @@ public class MediaPlaybackService extends Service {
                 return;
             }
 
+            if (mPlayListLen <= 0) {
+                Log.d(LOGTAG, "No play queue");
+                return;
+            }
+
             // Store the current file in the history, but keep the history at a
             // reasonable size
             if (mPlayPos >= 0) {
@@ -1191,6 +1303,10 @@ public class MediaPlaybackService extends Service {
                     } else {
                         // all done
                         gotoIdleState();
+                        if (mIsSupposedToBePlaying) {
+                            mIsSupposedToBePlaying = false;
+                            notifyChange(PLAYSTATE_CHANGED);
+                        }
                         return;
                     }
                 }
@@ -1233,12 +1349,10 @@ public class MediaPlaybackService extends Service {
     }
     
     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() {
@@ -1283,7 +1397,7 @@ public class MediaPlaybackService extends Service {
         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;
@@ -1296,7 +1410,7 @@ public class MediaPlaybackService extends Service {
     // A simple variation of Random that makes sure that the
     // value it returns is not equal to the value it returned
     // previously, unless the interval is 1.
-    private class Shuffler {
+    private static class Shuffler {
         private int mPrevious;
         private Random mRandom = new Random();
         public int nextInt(int interval) {
@@ -1320,10 +1434,10 @@ public class MediaPlaybackService extends Service {
                 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;
@@ -1397,7 +1511,7 @@ public class MediaPlaybackService extends Service {
      * @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++) {
@@ -1466,7 +1580,7 @@ public class MediaPlaybackService extends Service {
      * 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];
@@ -1496,6 +1610,9 @@ public class MediaPlaybackService extends Service {
             openCurrent();
             play();
             notifyChange(META_CHANGED);
+            if (mShuffleMode == SHUFFLE_AUTO) {
+                doAutoShuffleUpdate();
+            }
         }
     }
 
@@ -1508,12 +1625,12 @@ public class MediaPlaybackService extends Service {
         }
     }
     
-    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));
         }
     }
 
@@ -1526,12 +1643,12 @@ public class MediaPlaybackService extends Service {
         }
     }
 
-    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));
         }
     }
 
@@ -1663,6 +1780,7 @@ public class MediaPlaybackService extends Service {
         }
 
         public void start() {
+            MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
             mMediaPlayer.start();
         }
 
@@ -1671,6 +1789,14 @@ public class MediaPlaybackService extends Service {
             mIsInitialized = false;
         }
 
+        /**
+         * You CANNOT use this player anymore after calling release()
+         */
+        public void release() {
+            stop();
+            mMediaPlayer.release();
+        }
+        
         public void pause() {
             mMediaPlayer.pause();
         }
@@ -1712,6 +1838,7 @@ public class MediaPlaybackService extends Service {
                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
                     return true;
                 default:
+                    Log.d("MultiPlayer", "Error: " + what + "," + extra);
                     break;
                 }
                 return false;
@@ -1736,102 +1863,129 @@ public class MediaPlaybackService extends Service {
         }
     }
 
-    private final IMediaPlaybackService.Stub mBinder = new IMediaPlaybackService.Stub()
-    {
+    /*
+     * By making this a static class with a WeakReference to the Service, we
+     * ensure that the Service can be GCd even when the system process still
+     * has a remote reference to the stub.
+     */
+    static class ServiceStub extends IMediaPlaybackService.Stub {
+        WeakReference<MediaPlaybackService> mService;
+        
+        ServiceStub(MediaPlaybackService service) {
+            mService = new WeakReference<MediaPlaybackService>(service);
+        }
+
         public void openFileAsync(String path)
         {
-            MediaPlaybackService.this.openAsync(path);
+            mService.get().openAsync(path);
         }
         public void openFile(String path, boolean oneShot)
         {
-            MediaPlaybackService.this.open(path, oneShot);
+            mService.get().open(path, oneShot);
         }
-        public void open(int [] list, int position) {
-            MediaPlaybackService.this.open(list, position);
+        public void open(long [] list, int position) {
+            mService.get().open(list, position);
         }
         public int getQueuePosition() {
-            return MediaPlaybackService.this.getQueuePosition();
+            return mService.get().getQueuePosition();
         }
         public void setQueuePosition(int index) {
-            MediaPlaybackService.this.setQueuePosition(index);
+            mService.get().setQueuePosition(index);
         }
         public boolean isPlaying() {
-            return MediaPlaybackService.this.isPlaying();
+            return mService.get().isPlaying();
         }
         public void stop() {
-            MediaPlaybackService.this.stop();
+            mService.get().stop();
         }
         public void pause() {
-            MediaPlaybackService.this.pause();
+            mService.get().pause();
         }
         public void play() {
-            MediaPlaybackService.this.play();
+            mService.get().play();
         }
         public void prev() {
-            MediaPlaybackService.this.prev();
+            mService.get().prev();
         }
         public void next() {
-            MediaPlaybackService.this.next(true);
+            mService.get().next(true);
         }
         public String getTrackName() {
-            return MediaPlaybackService.this.getTrackName();
+            return mService.get().getTrackName();
         }
         public String getAlbumName() {
-            return MediaPlaybackService.this.getAlbumName();
+            return mService.get().getAlbumName();
         }
-        public int getAlbumId() {
-            return MediaPlaybackService.this.getAlbumId();
+        public long getAlbumId() {
+            return mService.get().getAlbumId();
         }
         public String getArtistName() {
-            return MediaPlaybackService.this.getArtistName();
+            return mService.get().getArtistName();
         }
-        public int getArtistId() {
-            return MediaPlaybackService.this.getArtistId();
+        public long getArtistId() {
+            return mService.get().getArtistId();
         }
-        public void enqueue(int [] list , int action) {
-            MediaPlaybackService.this.enqueue(list, action);
+        public void enqueue(long [] list , int action) {
+            mService.get().enqueue(list, action);
         }
-        public int [] getQueue() {
-            return MediaPlaybackService.this.getQueue();
+        public long [] getQueue() {
+            return mService.get().getQueue();
         }
         public void moveQueueItem(int from, int to) {
-            MediaPlaybackService.this.moveQueueItem(from, to);
+            mService.get().moveQueueItem(from, to);
         }
         public String getPath() {
-            return MediaPlaybackService.this.getPath();
+            return mService.get().getPath();
         }
-        public int getAudioId() {
-            return MediaPlaybackService.this.getAudioId();
+        public long getAudioId() {
+            return mService.get().getAudioId();
         }
         public long position() {
-            return MediaPlaybackService.this.position();
+            return mService.get().position();
         }
         public long duration() {
-            return MediaPlaybackService.this.duration();
+            return mService.get().duration();
         }
         public long seek(long pos) {
-            return MediaPlaybackService.this.seek(pos);
+            return mService.get().seek(pos);
         }
         public void setShuffleMode(int shufflemode) {
-            MediaPlaybackService.this.setShuffleMode(shufflemode);
+            mService.get().setShuffleMode(shufflemode);
         }
         public int getShuffleMode() {
-            return MediaPlaybackService.this.getShuffleMode();
+            return mService.get().getShuffleMode();
         }
         public int removeTracks(int first, int last) {
-            return MediaPlaybackService.this.removeTracks(first, last);
+            return mService.get().removeTracks(first, last);
         }
-        public int removeTrack(int id) {
-            return MediaPlaybackService.this.removeTrack(id);
+        public int removeTrack(long id) {
+            return mService.get().removeTrack(id);
         }
         public void setRepeatMode(int repeatmode) {
-            MediaPlaybackService.this.setRepeatMode(repeatmode);
+            mService.get().setRepeatMode(repeatmode);
         }
         public int getRepeatMode() {
-            return MediaPlaybackService.this.getRepeatMode();
+            return mService.get().getRepeatMode();
         }
         public int getMediaMountedCount() {
-            return MediaPlaybackService.this.getMediaMountedCount();
+            return mService.get().getMediaMountedCount();
         }
-    };
+
+    }
+
+    @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);
 }