Home | History | Annotate | Download | only in music
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.music;
     18 
     19 import android.app.Notification;
     20 import android.app.PendingIntent;
     21 import android.app.Service;
     22 import android.appwidget.AppWidgetManager;
     23 import android.content.ComponentName;
     24 import android.content.ContentResolver;
     25 import android.content.ContentUris;
     26 import android.content.ContentValues;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.content.BroadcastReceiver;
     31 import android.content.SharedPreferences;
     32 import android.content.SharedPreferences.Editor;
     33 import android.database.Cursor;
     34 import android.database.sqlite.SQLiteException;
     35 import android.media.audiofx.AudioEffect;
     36 import android.media.AudioManager;
     37 import android.media.AudioManager.OnAudioFocusChangeListener;
     38 import android.media.MediaPlayer;
     39 import android.net.Uri;
     40 import android.os.Handler;
     41 import android.os.IBinder;
     42 import android.os.Message;
     43 import android.os.PowerManager;
     44 import android.os.SystemClock;
     45 import android.os.PowerManager.WakeLock;
     46 import android.provider.MediaStore;
     47 import android.util.Log;
     48 import android.widget.RemoteViews;
     49 import android.widget.Toast;
     50 
     51 import java.io.FileDescriptor;
     52 import java.io.IOException;
     53 import java.io.PrintWriter;
     54 import java.lang.ref.WeakReference;
     55 import java.util.Random;
     56 import java.util.Vector;
     57 
     58 /**
     59  * Provides "background" audio playback capabilities, allowing the
     60  * user to switch between activities without stopping playback.
     61  */
     62 public class MediaPlaybackService extends Service {
     63     /** used to specify whether enqueue() should start playing
     64      * the new list of files right away, next or once all the currently
     65      * queued files have been played
     66      */
     67     public static final int NOW = 1;
     68     public static final int NEXT = 2;
     69     public static final int LAST = 3;
     70     public static final int PLAYBACKSERVICE_STATUS = 1;
     71 
     72     public static final int SHUFFLE_NONE = 0;
     73     public static final int SHUFFLE_NORMAL = 1;
     74     public static final int SHUFFLE_AUTO = 2;
     75 
     76     public static final int REPEAT_NONE = 0;
     77     public static final int REPEAT_CURRENT = 1;
     78     public static final int REPEAT_ALL = 2;
     79 
     80     public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
     81     public static final String META_CHANGED = "com.android.music.metachanged";
     82     public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
     83 
     84     public static final String SERVICECMD = "com.android.music.musicservicecommand";
     85     public static final String CMDNAME = "command";
     86     public static final String CMDTOGGLEPAUSE = "togglepause";
     87     public static final String CMDSTOP = "stop";
     88     public static final String CMDPAUSE = "pause";
     89     public static final String CMDPREVIOUS = "previous";
     90     public static final String CMDNEXT = "next";
     91 
     92     public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
     93     public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
     94     public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
     95     public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
     96 
     97     private static final int TRACK_ENDED = 1;
     98     private static final int RELEASE_WAKELOCK = 2;
     99     private static final int SERVER_DIED = 3;
    100     private static final int FOCUSCHANGE = 4;
    101     private static final int FADEDOWN = 5;
    102     private static final int FADEUP = 6;
    103     private static final int MAX_HISTORY_SIZE = 100;
    104 
    105     private MultiPlayer mPlayer;
    106     private String mFileToPlay;
    107     private int mShuffleMode = SHUFFLE_NONE;
    108     private int mRepeatMode = REPEAT_NONE;
    109     private int mMediaMountedCount = 0;
    110     private long [] mAutoShuffleList = null;
    111     private long [] mPlayList = null;
    112     private int mPlayListLen = 0;
    113     private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
    114     private Cursor mCursor;
    115     private int mPlayPos = -1;
    116     private static final String LOGTAG = "MediaPlaybackService";
    117     private final Shuffler mRand = new Shuffler();
    118     private int mOpenFailedCounter = 0;
    119     String[] mCursorCols = new String[] {
    120             "audio._id AS _id",             // index must match IDCOLIDX below
    121             MediaStore.Audio.Media.ARTIST,
    122             MediaStore.Audio.Media.ALBUM,
    123             MediaStore.Audio.Media.TITLE,
    124             MediaStore.Audio.Media.DATA,
    125             MediaStore.Audio.Media.MIME_TYPE,
    126             MediaStore.Audio.Media.ALBUM_ID,
    127             MediaStore.Audio.Media.ARTIST_ID,
    128             MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
    129             MediaStore.Audio.Media.BOOKMARK    // index must match BOOKMARKCOLIDX below
    130     };
    131     private final static int IDCOLIDX = 0;
    132     private final static int PODCASTCOLIDX = 8;
    133     private final static int BOOKMARKCOLIDX = 9;
    134     private BroadcastReceiver mUnmountReceiver = null;
    135     private WakeLock mWakeLock;
    136     private int mServiceStartId = -1;
    137     private boolean mServiceInUse = false;
    138     private boolean mIsSupposedToBePlaying = false;
    139     private boolean mQuietMode = false;
    140     private AudioManager mAudioManager;
    141     private boolean mQueueIsSaveable = true;
    142     // used to track what type of audio focus loss caused the playback to pause
    143     private boolean mPausedByTransientLossOfFocus = false;
    144 
    145     private SharedPreferences mPreferences;
    146     // We use this to distinguish between different cards when saving/restoring playlists.
    147     // This will have to change if we want to support multiple simultaneous cards.
    148     private int mCardId;
    149 
    150     private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
    151 
    152     // interval after which we stop the service when idle
    153     private static final int IDLE_DELAY = 60000;
    154 
    155     private Handler mMediaplayerHandler = new Handler() {
    156         float mCurrentVolume = 1.0f;
    157         @Override
    158         public void handleMessage(Message msg) {
    159             MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
    160             switch (msg.what) {
    161                 case FADEDOWN:
    162                     mCurrentVolume -= .05f;
    163                     if (mCurrentVolume > .2f) {
    164                         mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
    165                     } else {
    166                         mCurrentVolume = .2f;
    167                     }
    168                     mPlayer.setVolume(mCurrentVolume);
    169                     break;
    170                 case FADEUP:
    171                     mCurrentVolume += .01f;
    172                     if (mCurrentVolume < 1.0f) {
    173                         mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
    174                     } else {
    175                         mCurrentVolume = 1.0f;
    176                     }
    177                     mPlayer.setVolume(mCurrentVolume);
    178                     break;
    179                 case SERVER_DIED:
    180                     if (mIsSupposedToBePlaying) {
    181                         next(true);
    182                     } else {
    183                         // the server died when we were idle, so just
    184                         // reopen the same song (it will start again
    185                         // from the beginning though when the user
    186                         // restarts)
    187                         openCurrent();
    188                     }
    189                     break;
    190                 case TRACK_ENDED:
    191                     if (mRepeatMode == REPEAT_CURRENT) {
    192                         seek(0);
    193                         play();
    194                     } else {
    195                         next(false);
    196                     }
    197                     break;
    198                 case RELEASE_WAKELOCK:
    199                     mWakeLock.release();
    200                     break;
    201 
    202                 case FOCUSCHANGE:
    203                     // This code is here so we can better synchronize it with the code that
    204                     // handles fade-in
    205                     switch (msg.arg1) {
    206                         case AudioManager.AUDIOFOCUS_LOSS:
    207                             Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
    208                             if(isPlaying()) {
    209                                 mPausedByTransientLossOfFocus = false;
    210                             }
    211                             pause();
    212                             break;
    213                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
    214                             mMediaplayerHandler.removeMessages(FADEUP);
    215                             mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
    216                             break;
    217                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
    218                             Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
    219                             if(isPlaying()) {
    220                                 mPausedByTransientLossOfFocus = true;
    221                             }
    222                             pause();
    223                             break;
    224                         case AudioManager.AUDIOFOCUS_GAIN:
    225                             Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
    226                             if(!isPlaying() && mPausedByTransientLossOfFocus) {
    227                                 mPausedByTransientLossOfFocus = false;
    228                                 mCurrentVolume = 0f;
    229                                 mPlayer.setVolume(mCurrentVolume);
    230                                 play(); // also queues a fade-in
    231                             } else {
    232                                 mMediaplayerHandler.removeMessages(FADEDOWN);
    233                                 mMediaplayerHandler.sendEmptyMessage(FADEUP);
    234                             }
    235                             break;
    236                         default:
    237                             Log.e(LOGTAG, "Unknown audio focus change code");
    238                     }
    239                     break;
    240 
    241                 default:
    242                     break;
    243             }
    244         }
    245     };
    246 
    247     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    248         @Override
    249         public void onReceive(Context context, Intent intent) {
    250             String action = intent.getAction();
    251             String cmd = intent.getStringExtra("command");
    252             MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
    253             if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
    254                 next(true);
    255             } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
    256                 prev();
    257             } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
    258                 if (isPlaying()) {
    259                     pause();
    260                     mPausedByTransientLossOfFocus = false;
    261                 } else {
    262                     play();
    263                 }
    264             } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
    265                 pause();
    266                 mPausedByTransientLossOfFocus = false;
    267             } else if (CMDSTOP.equals(cmd)) {
    268                 pause();
    269                 mPausedByTransientLossOfFocus = false;
    270                 seek(0);
    271             } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
    272                 // Someone asked us to refresh a set of specific widgets, probably
    273                 // because they were just added.
    274                 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
    275                 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
    276             }
    277         }
    278     };
    279 
    280     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
    281         public void onAudioFocusChange(int focusChange) {
    282             mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
    283         }
    284     };
    285 
    286     public MediaPlaybackService() {
    287     }
    288 
    289     @Override
    290     public void onCreate() {
    291         super.onCreate();
    292 
    293         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    294         mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
    295                 MediaButtonIntentReceiver.class.getName()));
    296 
    297         mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
    298         mCardId = MusicUtils.getCardId(this);
    299 
    300         registerExternalStorageListener();
    301 
    302         // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
    303         mPlayer = new MultiPlayer();
    304         mPlayer.setHandler(mMediaplayerHandler);
    305 
    306         reloadQueue();
    307 
    308         IntentFilter commandFilter = new IntentFilter();
    309         commandFilter.addAction(SERVICECMD);
    310         commandFilter.addAction(TOGGLEPAUSE_ACTION);
    311         commandFilter.addAction(PAUSE_ACTION);
    312         commandFilter.addAction(NEXT_ACTION);
    313         commandFilter.addAction(PREVIOUS_ACTION);
    314         registerReceiver(mIntentReceiver, commandFilter);
    315 
    316         PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
    317         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
    318         mWakeLock.setReferenceCounted(false);
    319 
    320         // If the service was idle, but got killed before it stopped itself, the
    321         // system will relaunch it. Make sure it gets stopped again in that case.
    322         Message msg = mDelayedStopHandler.obtainMessage();
    323         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    324     }
    325 
    326     @Override
    327     public void onDestroy() {
    328         // Check that we're not being destroyed while something is still playing.
    329         if (isPlaying()) {
    330             Log.e(LOGTAG, "Service being destroyed while still playing.");
    331         }
    332         // release all MediaPlayer resources, including the native player and wakelocks
    333         Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
    334         i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
    335         i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
    336         sendBroadcast(i);
    337         mPlayer.release();
    338         mPlayer = null;
    339 
    340         mAudioManager.abandonAudioFocus(mAudioFocusListener);
    341 
    342         // make sure there aren't any other messages coming
    343         mDelayedStopHandler.removeCallbacksAndMessages(null);
    344         mMediaplayerHandler.removeCallbacksAndMessages(null);
    345 
    346         if (mCursor != null) {
    347             mCursor.close();
    348             mCursor = null;
    349         }
    350 
    351         unregisterReceiver(mIntentReceiver);
    352         if (mUnmountReceiver != null) {
    353             unregisterReceiver(mUnmountReceiver);
    354             mUnmountReceiver = null;
    355         }
    356         mWakeLock.release();
    357         super.onDestroy();
    358     }
    359 
    360     private final char hexdigits [] = new char [] {
    361             '0', '1', '2', '3',
    362             '4', '5', '6', '7',
    363             '8', '9', 'a', 'b',
    364             'c', 'd', 'e', 'f'
    365     };
    366 
    367     private void saveQueue(boolean full) {
    368         if (!mQueueIsSaveable) {
    369             return;
    370         }
    371 
    372         Editor ed = mPreferences.edit();
    373         //long start = System.currentTimeMillis();
    374         if (full) {
    375             StringBuilder q = new StringBuilder();
    376 
    377             // The current playlist is saved as a list of "reverse hexadecimal"
    378             // numbers, which we can generate faster than normal decimal or
    379             // hexadecimal numbers, which in turn allows us to save the playlist
    380             // more often without worrying too much about performance.
    381             // (saving the full state takes about 40 ms under no-load conditions
    382             // on the phone)
    383             int len = mPlayListLen;
    384             for (int i = 0; i < len; i++) {
    385                 long n = mPlayList[i];
    386                 if (n < 0) {
    387                     continue;
    388                 } else if (n == 0) {
    389                     q.append("0;");
    390                 } else {
    391                     while (n != 0) {
    392                         int digit = (int)(n & 0xf);
    393                         n >>>= 4;
    394                         q.append(hexdigits[digit]);
    395                     }
    396                     q.append(";");
    397                 }
    398             }
    399             //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
    400             ed.putString("queue", q.toString());
    401             ed.putInt("cardid", mCardId);
    402             if (mShuffleMode != SHUFFLE_NONE) {
    403                 // In shuffle mode we need to save the history too
    404                 len = mHistory.size();
    405                 q.setLength(0);
    406                 for (int i = 0; i < len; i++) {
    407                     int n = mHistory.get(i);
    408                     if (n == 0) {
    409                         q.append("0;");
    410                     } else {
    411                         while (n != 0) {
    412                             int digit = (n & 0xf);
    413                             n >>>= 4;
    414                             q.append(hexdigits[digit]);
    415                         }
    416                         q.append(";");
    417                     }
    418                 }
    419                 ed.putString("history", q.toString());
    420             }
    421         }
    422         ed.putInt("curpos", mPlayPos);
    423         if (mPlayer.isInitialized()) {
    424             ed.putLong("seekpos", mPlayer.position());
    425         }
    426         ed.putInt("repeatmode", mRepeatMode);
    427         ed.putInt("shufflemode", mShuffleMode);
    428         SharedPreferencesCompat.apply(ed);
    429 
    430         //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
    431     }
    432 
    433     private void reloadQueue() {
    434         String q = null;
    435 
    436         boolean newstyle = false;
    437         int id = mCardId;
    438         if (mPreferences.contains("cardid")) {
    439             newstyle = true;
    440             id = mPreferences.getInt("cardid", ~mCardId);
    441         }
    442         if (id == mCardId) {
    443             // Only restore the saved playlist if the card is still
    444             // the same one as when the playlist was saved
    445             q = mPreferences.getString("queue", "");
    446         }
    447         int qlen = q != null ? q.length() : 0;
    448         if (qlen > 1) {
    449             //Log.i("@@@@ service", "loaded queue: " + q);
    450             int plen = 0;
    451             int n = 0;
    452             int shift = 0;
    453             for (int i = 0; i < qlen; i++) {
    454                 char c = q.charAt(i);
    455                 if (c == ';') {
    456                     ensurePlayListCapacity(plen + 1);
    457                     mPlayList[plen] = n;
    458                     plen++;
    459                     n = 0;
    460                     shift = 0;
    461                 } else {
    462                     if (c >= '0' && c <= '9') {
    463                         n += ((c - '0') << shift);
    464                     } else if (c >= 'a' && c <= 'f') {
    465                         n += ((10 + c - 'a') << shift);
    466                     } else {
    467                         // bogus playlist data
    468                         plen = 0;
    469                         break;
    470                     }
    471                     shift += 4;
    472                 }
    473             }
    474             mPlayListLen = plen;
    475 
    476             int pos = mPreferences.getInt("curpos", 0);
    477             if (pos < 0 || pos >= mPlayListLen) {
    478                 // The saved playlist is bogus, discard it
    479                 mPlayListLen = 0;
    480                 return;
    481             }
    482             mPlayPos = pos;
    483 
    484             // When reloadQueue is called in response to a card-insertion,
    485             // we might not be able to query the media provider right away.
    486             // To deal with this, try querying for the current file, and if
    487             // that fails, wait a while and try again. If that too fails,
    488             // assume there is a problem and don't restore the state.
    489             Cursor crsr = MusicUtils.query(this,
    490                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    491                         new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
    492             if (crsr == null || crsr.getCount() == 0) {
    493                 // wait a bit and try again
    494                 SystemClock.sleep(3000);
    495                 crsr = getContentResolver().query(
    496                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    497                         mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
    498             }
    499             if (crsr != null) {
    500                 crsr.close();
    501             }
    502 
    503             // Make sure we don't auto-skip to the next song, since that
    504             // also starts playback. What could happen in that case is:
    505             // - music is paused
    506             // - go to UMS and delete some files, including the currently playing one
    507             // - come back from UMS
    508             // (time passes)
    509             // - music app is killed for some reason (out of memory)
    510             // - music service is restarted, service restores state, doesn't find
    511             //   the "current" file, goes to the next and: playback starts on its
    512             //   own, potentially at some random inconvenient time.
    513             mOpenFailedCounter = 20;
    514             mQuietMode = true;
    515             openCurrent();
    516             mQuietMode = false;
    517             if (!mPlayer.isInitialized()) {
    518                 // couldn't restore the saved state
    519                 mPlayListLen = 0;
    520                 return;
    521             }
    522 
    523             long seekpos = mPreferences.getLong("seekpos", 0);
    524             seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
    525             Log.d(LOGTAG, "restored queue, currently at position "
    526                     + position() + "/" + duration()
    527                     + " (requested " + seekpos + ")");
    528 
    529             int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
    530             if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
    531                 repmode = REPEAT_NONE;
    532             }
    533             mRepeatMode = repmode;
    534 
    535             int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
    536             if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
    537                 shufmode = SHUFFLE_NONE;
    538             }
    539             if (shufmode != SHUFFLE_NONE) {
    540                 // in shuffle mode we need to restore the history too
    541                 q = mPreferences.getString("history", "");
    542                 qlen = q != null ? q.length() : 0;
    543                 if (qlen > 1) {
    544                     plen = 0;
    545                     n = 0;
    546                     shift = 0;
    547                     mHistory.clear();
    548                     for (int i = 0; i < qlen; i++) {
    549                         char c = q.charAt(i);
    550                         if (c == ';') {
    551                             if (n >= mPlayListLen) {
    552                                 // bogus history data
    553                                 mHistory.clear();
    554                                 break;
    555                             }
    556                             mHistory.add(n);
    557                             n = 0;
    558                             shift = 0;
    559                         } else {
    560                             if (c >= '0' && c <= '9') {
    561                                 n += ((c - '0') << shift);
    562                             } else if (c >= 'a' && c <= 'f') {
    563                                 n += ((10 + c - 'a') << shift);
    564                             } else {
    565                                 // bogus history data
    566                                 mHistory.clear();
    567                                 break;
    568                             }
    569                             shift += 4;
    570                         }
    571                     }
    572                 }
    573             }
    574             if (shufmode == SHUFFLE_AUTO) {
    575                 if (! makeAutoShuffleList()) {
    576                     shufmode = SHUFFLE_NONE;
    577                 }
    578             }
    579             mShuffleMode = shufmode;
    580         }
    581     }
    582 
    583     @Override
    584     public IBinder onBind(Intent intent) {
    585         mDelayedStopHandler.removeCallbacksAndMessages(null);
    586         mServiceInUse = true;
    587         return mBinder;
    588     }
    589 
    590     @Override
    591     public void onRebind(Intent intent) {
    592         mDelayedStopHandler.removeCallbacksAndMessages(null);
    593         mServiceInUse = true;
    594     }
    595 
    596     @Override
    597     public int onStartCommand(Intent intent, int flags, int startId) {
    598         mServiceStartId = startId;
    599         mDelayedStopHandler.removeCallbacksAndMessages(null);
    600 
    601         if (intent != null) {
    602             String action = intent.getAction();
    603             String cmd = intent.getStringExtra("command");
    604             MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
    605 
    606             if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
    607                 next(true);
    608             } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
    609                 if (position() < 2000) {
    610                     prev();
    611                 } else {
    612                     seek(0);
    613                     play();
    614                 }
    615             } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
    616                 if (isPlaying()) {
    617                     pause();
    618                     mPausedByTransientLossOfFocus = false;
    619                 } else {
    620                     play();
    621                 }
    622             } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
    623                 pause();
    624                 mPausedByTransientLossOfFocus = false;
    625             } else if (CMDSTOP.equals(cmd)) {
    626                 pause();
    627                 mPausedByTransientLossOfFocus = false;
    628                 seek(0);
    629             }
    630         }
    631 
    632         // make sure the service will shut down on its own if it was
    633         // just started but not bound to and nothing is playing
    634         mDelayedStopHandler.removeCallbacksAndMessages(null);
    635         Message msg = mDelayedStopHandler.obtainMessage();
    636         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    637         return START_STICKY;
    638     }
    639 
    640     @Override
    641     public boolean onUnbind(Intent intent) {
    642         mServiceInUse = false;
    643 
    644         // Take a snapshot of the current playlist
    645         saveQueue(true);
    646 
    647         if (isPlaying() || mPausedByTransientLossOfFocus) {
    648             // something is currently playing, or will be playing once
    649             // an in-progress action requesting audio focus ends, so don't stop the service now.
    650             return true;
    651         }
    652 
    653         // If there is a playlist but playback is paused, then wait a while
    654         // before stopping the service, so that pause/resume isn't slow.
    655         // Also delay stopping the service if we're transitioning between tracks.
    656         if (mPlayListLen > 0  || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
    657             Message msg = mDelayedStopHandler.obtainMessage();
    658             mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    659             return true;
    660         }
    661 
    662         // No active playlist, OK to stop the service right now
    663         stopSelf(mServiceStartId);
    664         return true;
    665     }
    666 
    667     private Handler mDelayedStopHandler = new Handler() {
    668         @Override
    669         public void handleMessage(Message msg) {
    670             // Check again to make sure nothing is playing right now
    671             if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
    672                     || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
    673                 return;
    674             }
    675             // save the queue again, because it might have changed
    676             // since the user exited the music app (because of
    677             // party-shuffle or because the play-position changed)
    678             saveQueue(true);
    679             stopSelf(mServiceStartId);
    680         }
    681     };
    682 
    683     /**
    684      * Called when we receive a ACTION_MEDIA_EJECT notification.
    685      *
    686      * @param storagePath path to mount point for the removed media
    687      */
    688     public void closeExternalStorageFiles(String storagePath) {
    689         // stop playback and clean up if the SD card is going to be unmounted.
    690         stop(true);
    691         notifyChange(QUEUE_CHANGED);
    692         notifyChange(META_CHANGED);
    693     }
    694 
    695     /**
    696      * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
    697      * The intent will call closeExternalStorageFiles() if the external media
    698      * is going to be ejected, so applications can clean up any files they have open.
    699      */
    700     public void registerExternalStorageListener() {
    701         if (mUnmountReceiver == null) {
    702             mUnmountReceiver = new BroadcastReceiver() {
    703                 @Override
    704                 public void onReceive(Context context, Intent intent) {
    705                     String action = intent.getAction();
    706                     if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
    707                         saveQueue(true);
    708                         mQueueIsSaveable = false;
    709                         closeExternalStorageFiles(intent.getData().getPath());
    710                     } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
    711                         mMediaMountedCount++;
    712                         mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
    713                         reloadQueue();
    714                         mQueueIsSaveable = true;
    715                         notifyChange(QUEUE_CHANGED);
    716                         notifyChange(META_CHANGED);
    717                     }
    718                 }
    719             };
    720             IntentFilter iFilter = new IntentFilter();
    721             iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
    722             iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
    723             iFilter.addDataScheme("file");
    724             registerReceiver(mUnmountReceiver, iFilter);
    725         }
    726     }
    727 
    728     /**
    729      * Notify the change-receivers that something has changed.
    730      * The intent that is sent contains the following data
    731      * for the currently playing track:
    732      * "id" - Integer: the database row ID
    733      * "artist" - String: the name of the artist
    734      * "album" - String: the name of the album
    735      * "track" - String: the name of the track
    736      * The intent has an action that is one of
    737      * "com.android.music.metachanged"
    738      * "com.android.music.queuechanged",
    739      * "com.android.music.playbackcomplete"
    740      * "com.android.music.playstatechanged"
    741      * respectively indicating that a new track has
    742      * started playing, that the playback queue has
    743      * changed, that playback has stopped because
    744      * the last file in the list has been played,
    745      * or that the play-state changed (paused/resumed).
    746      */
    747     private void notifyChange(String what) {
    748 
    749         Intent i = new Intent(what);
    750         i.putExtra("id", Long.valueOf(getAudioId()));
    751         i.putExtra("artist", getArtistName());
    752         i.putExtra("album",getAlbumName());
    753         i.putExtra("track", getTrackName());
    754         i.putExtra("playing", isPlaying());
    755         sendStickyBroadcast(i);
    756 
    757         if (what.equals(QUEUE_CHANGED)) {
    758             saveQueue(true);
    759         } else {
    760             saveQueue(false);
    761         }
    762 
    763         // Share this notification directly with our widgets
    764         mAppWidgetProvider.notifyChange(this, what);
    765     }
    766 
    767     private void ensurePlayListCapacity(int size) {
    768         if (mPlayList == null || size > mPlayList.length) {
    769             // reallocate at 2x requested size so we don't
    770             // need to grow and copy the array for every
    771             // insert
    772             long [] newlist = new long[size * 2];
    773             int len = mPlayList != null ? mPlayList.length : mPlayListLen;
    774             for (int i = 0; i < len; i++) {
    775                 newlist[i] = mPlayList[i];
    776             }
    777             mPlayList = newlist;
    778         }
    779         // FIXME: shrink the array when the needed size is much smaller
    780         // than the allocated size
    781     }
    782 
    783     // insert the list of songs at the specified position in the playlist
    784     private void addToPlayList(long [] list, int position) {
    785         int addlen = list.length;
    786         if (position < 0) { // overwrite
    787             mPlayListLen = 0;
    788             position = 0;
    789         }
    790         ensurePlayListCapacity(mPlayListLen + addlen);
    791         if (position > mPlayListLen) {
    792             position = mPlayListLen;
    793         }
    794 
    795         // move part of list after insertion point
    796         int tailsize = mPlayListLen - position;
    797         for (int i = tailsize ; i > 0 ; i--) {
    798             mPlayList[position + i] = mPlayList[position + i - addlen];
    799         }
    800 
    801         // copy list into playlist
    802         for (int i = 0; i < addlen; i++) {
    803             mPlayList[position + i] = list[i];
    804         }
    805         mPlayListLen += addlen;
    806         if (mPlayListLen == 0) {
    807             mCursor.close();
    808             mCursor = null;
    809             notifyChange(META_CHANGED);
    810         }
    811     }
    812 
    813     /**
    814      * Appends a list of tracks to the current playlist.
    815      * If nothing is playing currently, playback will be started at
    816      * the first track.
    817      * If the action is NOW, playback will switch to the first of
    818      * the new tracks immediately.
    819      * @param list The list of tracks to append.
    820      * @param action NOW, NEXT or LAST
    821      */
    822     public void enqueue(long [] list, int action) {
    823         synchronized(this) {
    824             if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
    825                 addToPlayList(list, mPlayPos + 1);
    826                 notifyChange(QUEUE_CHANGED);
    827             } else {
    828                 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
    829                 addToPlayList(list, Integer.MAX_VALUE);
    830                 notifyChange(QUEUE_CHANGED);
    831                 if (action == NOW) {
    832                     mPlayPos = mPlayListLen - list.length;
    833                     openCurrent();
    834                     play();
    835                     notifyChange(META_CHANGED);
    836                     return;
    837                 }
    838             }
    839             if (mPlayPos < 0) {
    840                 mPlayPos = 0;
    841                 openCurrent();
    842                 play();
    843                 notifyChange(META_CHANGED);
    844             }
    845         }
    846     }
    847 
    848     /**
    849      * Replaces the current playlist with a new list,
    850      * and prepares for starting playback at the specified
    851      * position in the list, or a random position if the
    852      * specified position is 0.
    853      * @param list The new list of tracks.
    854      */
    855     public void open(long [] list, int position) {
    856         synchronized (this) {
    857             if (mShuffleMode == SHUFFLE_AUTO) {
    858                 mShuffleMode = SHUFFLE_NORMAL;
    859             }
    860             long oldId = getAudioId();
    861             int listlength = list.length;
    862             boolean newlist = true;
    863             if (mPlayListLen == listlength) {
    864                 // possible fast path: list might be the same
    865                 newlist = false;
    866                 for (int i = 0; i < listlength; i++) {
    867                     if (list[i] != mPlayList[i]) {
    868                         newlist = true;
    869                         break;
    870                     }
    871                 }
    872             }
    873             if (newlist) {
    874                 addToPlayList(list, -1);
    875                 notifyChange(QUEUE_CHANGED);
    876             }
    877             int oldpos = mPlayPos;
    878             if (position >= 0) {
    879                 mPlayPos = position;
    880             } else {
    881                 mPlayPos = mRand.nextInt(mPlayListLen);
    882             }
    883             mHistory.clear();
    884 
    885             saveBookmarkIfNeeded();
    886             openCurrent();
    887             if (oldId != getAudioId()) {
    888                 notifyChange(META_CHANGED);
    889             }
    890         }
    891     }
    892 
    893     /**
    894      * Moves the item at index1 to index2.
    895      * @param index1
    896      * @param index2
    897      */
    898     public void moveQueueItem(int index1, int index2) {
    899         synchronized (this) {
    900             if (index1 >= mPlayListLen) {
    901                 index1 = mPlayListLen - 1;
    902             }
    903             if (index2 >= mPlayListLen) {
    904                 index2 = mPlayListLen - 1;
    905             }
    906             if (index1 < index2) {
    907                 long tmp = mPlayList[index1];
    908                 for (int i = index1; i < index2; i++) {
    909                     mPlayList[i] = mPlayList[i+1];
    910                 }
    911                 mPlayList[index2] = tmp;
    912                 if (mPlayPos == index1) {
    913                     mPlayPos = index2;
    914                 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
    915                         mPlayPos--;
    916                 }
    917             } else if (index2 < index1) {
    918                 long tmp = mPlayList[index1];
    919                 for (int i = index1; i > index2; i--) {
    920                     mPlayList[i] = mPlayList[i-1];
    921                 }
    922                 mPlayList[index2] = tmp;
    923                 if (mPlayPos == index1) {
    924                     mPlayPos = index2;
    925                 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
    926                         mPlayPos++;
    927                 }
    928             }
    929             notifyChange(QUEUE_CHANGED);
    930         }
    931     }
    932 
    933     /**
    934      * Returns the current play list
    935      * @return An array of integers containing the IDs of the tracks in the play list
    936      */
    937     public long [] getQueue() {
    938         synchronized (this) {
    939             int len = mPlayListLen;
    940             long [] list = new long[len];
    941             for (int i = 0; i < len; i++) {
    942                 list[i] = mPlayList[i];
    943             }
    944             return list;
    945         }
    946     }
    947 
    948     private void openCurrent() {
    949         synchronized (this) {
    950             if (mCursor != null) {
    951                 mCursor.close();
    952                 mCursor = null;
    953             }
    954 
    955             if (mPlayListLen == 0) {
    956                 return;
    957             }
    958             stop(false);
    959 
    960             String id = String.valueOf(mPlayList[mPlayPos]);
    961 
    962             mCursor = getContentResolver().query(
    963                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    964                     mCursorCols, "_id=" + id , null, null);
    965             if (mCursor != null) {
    966                 mCursor.moveToFirst();
    967                 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
    968                 // go to bookmark if needed
    969                 if (isPodcast()) {
    970                     long bookmark = getBookmark();
    971                     // Start playing a little bit before the bookmark,
    972                     // so it's easier to get back in to the narrative.
    973                     seek(bookmark - 5000);
    974                 }
    975             }
    976         }
    977     }
    978 
    979     /**
    980      * Opens the specified file and readies it for playback.
    981      *
    982      * @param path The full path of the file to be opened.
    983      */
    984     public void open(String path) {
    985         synchronized (this) {
    986             if (path == null) {
    987                 return;
    988             }
    989 
    990             // if mCursor is null, try to associate path with a database cursor
    991             if (mCursor == null) {
    992 
    993                 ContentResolver resolver = getContentResolver();
    994                 Uri uri;
    995                 String where;
    996                 String selectionArgs[];
    997                 if (path.startsWith("content://media/")) {
    998                     uri = Uri.parse(path);
    999                     where = null;
   1000                     selectionArgs = null;
   1001                 } else {
   1002                    uri = MediaStore.Audio.Media.getContentUriForPath(path);
   1003                    where = MediaStore.Audio.Media.DATA + "=?";
   1004                    selectionArgs = new String[] { path };
   1005                 }
   1006 
   1007                 try {
   1008                     mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
   1009                     if  (mCursor != null) {
   1010                         if (mCursor.getCount() == 0) {
   1011                             mCursor.close();
   1012                             mCursor = null;
   1013                         } else {
   1014                             mCursor.moveToNext();
   1015                             ensurePlayListCapacity(1);
   1016                             mPlayListLen = 1;
   1017                             mPlayList[0] = mCursor.getLong(IDCOLIDX);
   1018                             mPlayPos = 0;
   1019                         }
   1020                     }
   1021                 } catch (UnsupportedOperationException ex) {
   1022                 }
   1023             }
   1024             mFileToPlay = path;
   1025             mPlayer.setDataSource(mFileToPlay);
   1026             if (! mPlayer.isInitialized()) {
   1027                 stop(true);
   1028                 if (mOpenFailedCounter++ < 10 &&  mPlayListLen > 1) {
   1029                     // beware: this ends up being recursive because next() calls open() again.
   1030                     next(false);
   1031                 }
   1032                 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
   1033                     // need to make sure we only shows this once
   1034                     mOpenFailedCounter = 0;
   1035                     if (!mQuietMode) {
   1036                         Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
   1037                     }
   1038                     Log.d(LOGTAG, "Failed to open file for playback");
   1039                 }
   1040             } else {
   1041                 mOpenFailedCounter = 0;
   1042             }
   1043         }
   1044     }
   1045 
   1046     /**
   1047      * Starts playback of a previously opened file.
   1048      */
   1049     public void play() {
   1050         mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
   1051                 AudioManager.AUDIOFOCUS_GAIN);
   1052         mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
   1053                 MediaButtonIntentReceiver.class.getName()));
   1054 
   1055         if (mPlayer.isInitialized()) {
   1056             // if we are at the end of the song, go to the next song first
   1057             long duration = mPlayer.duration();
   1058             if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
   1059                 mPlayer.position() >= duration - 2000) {
   1060                 next(true);
   1061             }
   1062 
   1063             mPlayer.start();
   1064             // make sure we fade in, in case a previous fadein was stopped because
   1065             // of another focus loss
   1066             mMediaplayerHandler.removeMessages(FADEDOWN);
   1067             mMediaplayerHandler.sendEmptyMessage(FADEUP);
   1068 
   1069             RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
   1070             views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
   1071             if (getAudioId() < 0) {
   1072                 // streaming
   1073                 views.setTextViewText(R.id.trackname, getPath());
   1074                 views.setTextViewText(R.id.artistalbum, null);
   1075             } else {
   1076                 String artist = getArtistName();
   1077                 views.setTextViewText(R.id.trackname, getTrackName());
   1078                 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
   1079                     artist = getString(R.string.unknown_artist_name);
   1080                 }
   1081                 String album = getAlbumName();
   1082                 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
   1083                     album = getString(R.string.unknown_album_name);
   1084                 }
   1085 
   1086                 views.setTextViewText(R.id.artistalbum,
   1087                         getString(R.string.notification_artist_album, artist, album)
   1088                         );
   1089             }
   1090 
   1091             Notification status = new Notification();
   1092             status.contentView = views;
   1093             status.flags |= Notification.FLAG_ONGOING_EVENT;
   1094             status.icon = R.drawable.stat_notify_musicplayer;
   1095             status.contentIntent = PendingIntent.getActivity(this, 0,
   1096                     new Intent("com.android.music.PLAYBACK_VIEWER")
   1097                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
   1098             startForeground(PLAYBACKSERVICE_STATUS, status);
   1099             if (!mIsSupposedToBePlaying) {
   1100                 mIsSupposedToBePlaying = true;
   1101                 notifyChange(PLAYSTATE_CHANGED);
   1102             }
   1103 
   1104         } else if (mPlayListLen <= 0) {
   1105             // This is mostly so that if you press 'play' on a bluetooth headset
   1106             // without every having played anything before, it will still play
   1107             // something.
   1108             setShuffleMode(SHUFFLE_AUTO);
   1109         }
   1110     }
   1111 
   1112     private void stop(boolean remove_status_icon) {
   1113         if (mPlayer.isInitialized()) {
   1114             mPlayer.stop();
   1115         }
   1116         mFileToPlay = null;
   1117         if (mCursor != null) {
   1118             mCursor.close();
   1119             mCursor = null;
   1120         }
   1121         if (remove_status_icon) {
   1122             gotoIdleState();
   1123         } else {
   1124             stopForeground(false);
   1125         }
   1126         if (remove_status_icon) {
   1127             mIsSupposedToBePlaying = false;
   1128         }
   1129     }
   1130 
   1131     /**
   1132      * Stops playback.
   1133      */
   1134     public void stop() {
   1135         stop(true);
   1136     }
   1137 
   1138     /**
   1139      * Pauses playback (call play() to resume)
   1140      */
   1141     public void pause() {
   1142         synchronized(this) {
   1143             mMediaplayerHandler.removeMessages(FADEUP);
   1144             if (isPlaying()) {
   1145                 mPlayer.pause();
   1146                 gotoIdleState();
   1147                 mIsSupposedToBePlaying = false;
   1148                 notifyChange(PLAYSTATE_CHANGED);
   1149                 saveBookmarkIfNeeded();
   1150             }
   1151         }
   1152     }
   1153 
   1154     /** Returns whether something is currently playing
   1155      *
   1156      * @return true if something is playing (or will be playing shortly, in case
   1157      * we're currently transitioning between tracks), false if not.
   1158      */
   1159     public boolean isPlaying() {
   1160         return mIsSupposedToBePlaying;
   1161     }
   1162 
   1163     /*
   1164       Desired behavior for prev/next/shuffle:
   1165 
   1166       - NEXT will move to the next track in the list when not shuffling, and to
   1167         a track randomly picked from the not-yet-played tracks when shuffling.
   1168         If all tracks have already been played, pick from the full set, but
   1169         avoid picking the previously played track if possible.
   1170       - when shuffling, PREV will go to the previously played track. Hitting PREV
   1171         again will go to the track played before that, etc. When the start of the
   1172         history has been reached, PREV is a no-op.
   1173         When not shuffling, PREV will go to the sequentially previous track (the
   1174         difference with the shuffle-case is mainly that when not shuffling, the
   1175         user can back up to tracks that are not in the history).
   1176 
   1177         Example:
   1178         When playing an album with 10 tracks from the start, and enabling shuffle
   1179         while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
   1180         the final play order might be 1-2-3-4-5-8-10-6-9-7.
   1181         When hitting 'prev' 8 times while playing track 7 in this example, the
   1182         user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
   1183         a random track will be picked again. If at any time user disables shuffling
   1184         the next/previous track will be picked in sequential order again.
   1185      */
   1186 
   1187     public void prev() {
   1188         synchronized (this) {
   1189             if (mShuffleMode == SHUFFLE_NORMAL) {
   1190                 // go to previously-played track and remove it from the history
   1191                 int histsize = mHistory.size();
   1192                 if (histsize == 0) {
   1193                     // prev is a no-op
   1194                     return;
   1195                 }
   1196                 Integer pos = mHistory.remove(histsize - 1);
   1197                 mPlayPos = pos.intValue();
   1198             } else {
   1199                 if (mPlayPos > 0) {
   1200                     mPlayPos--;
   1201                 } else {
   1202                     mPlayPos = mPlayListLen - 1;
   1203                 }
   1204             }
   1205             saveBookmarkIfNeeded();
   1206             stop(false);
   1207             openCurrent();
   1208             play();
   1209             notifyChange(META_CHANGED);
   1210         }
   1211     }
   1212 
   1213     public void next(boolean force) {
   1214         synchronized (this) {
   1215             if (mPlayListLen <= 0) {
   1216                 Log.d(LOGTAG, "No play queue");
   1217                 return;
   1218             }
   1219 
   1220             if (mShuffleMode == SHUFFLE_NORMAL) {
   1221                 // Pick random next track from the not-yet-played ones
   1222                 // TODO: make it work right after adding/removing items in the queue.
   1223 
   1224                 // Store the current file in the history, but keep the history at a
   1225                 // reasonable size
   1226                 if (mPlayPos >= 0) {
   1227                     mHistory.add(mPlayPos);
   1228                 }
   1229                 if (mHistory.size() > MAX_HISTORY_SIZE) {
   1230                     mHistory.removeElementAt(0);
   1231                 }
   1232 
   1233                 int numTracks = mPlayListLen;
   1234                 int[] tracks = new int[numTracks];
   1235                 for (int i=0;i < numTracks; i++) {
   1236                     tracks[i] = i;
   1237                 }
   1238 
   1239                 int numHistory = mHistory.size();
   1240                 int numUnplayed = numTracks;
   1241                 for (int i=0;i < numHistory; i++) {
   1242                     int idx = mHistory.get(i).intValue();
   1243                     if (idx < numTracks && tracks[idx] >= 0) {
   1244                         numUnplayed--;
   1245                         tracks[idx] = -1;
   1246                     }
   1247                 }
   1248 
   1249                 // 'numUnplayed' now indicates how many tracks have not yet
   1250                 // been played, and 'tracks' contains the indices of those
   1251                 // tracks.
   1252                 if (numUnplayed <=0) {
   1253                     // everything's already been played
   1254                     if (mRepeatMode == REPEAT_ALL || force) {
   1255                         //pick from full set
   1256                         numUnplayed = numTracks;
   1257                         for (int i=0;i < numTracks; i++) {
   1258                             tracks[i] = i;
   1259                         }
   1260                     } else {
   1261                         // all done
   1262                         gotoIdleState();
   1263                         if (mIsSupposedToBePlaying) {
   1264                             mIsSupposedToBePlaying = false;
   1265                             notifyChange(PLAYSTATE_CHANGED);
   1266                         }
   1267                         return;
   1268                     }
   1269                 }
   1270                 int skip = mRand.nextInt(numUnplayed);
   1271                 int cnt = -1;
   1272                 while (true) {
   1273                     while (tracks[++cnt] < 0)
   1274                         ;
   1275                     skip--;
   1276                     if (skip < 0) {
   1277                         break;
   1278                     }
   1279                 }
   1280                 mPlayPos = cnt;
   1281             } else if (mShuffleMode == SHUFFLE_AUTO) {
   1282                 doAutoShuffleUpdate();
   1283                 mPlayPos++;
   1284             } else {
   1285                 if (mPlayPos >= mPlayListLen - 1) {
   1286                     // we're at the end of the list
   1287                     if (mRepeatMode == REPEAT_NONE && !force) {
   1288                         // all done
   1289                         gotoIdleState();
   1290                         mIsSupposedToBePlaying = false;
   1291                         notifyChange(PLAYSTATE_CHANGED);
   1292                         return;
   1293                     } else if (mRepeatMode == REPEAT_ALL || force) {
   1294                         mPlayPos = 0;
   1295                     }
   1296                 } else {
   1297                     mPlayPos++;
   1298                 }
   1299             }
   1300             saveBookmarkIfNeeded();
   1301             stop(false);
   1302             openCurrent();
   1303             play();
   1304             notifyChange(META_CHANGED);
   1305         }
   1306     }
   1307 
   1308     private void gotoIdleState() {
   1309         mDelayedStopHandler.removeCallbacksAndMessages(null);
   1310         Message msg = mDelayedStopHandler.obtainMessage();
   1311         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
   1312         stopForeground(true);
   1313     }
   1314 
   1315     private void saveBookmarkIfNeeded() {
   1316         try {
   1317             if (isPodcast()) {
   1318                 long pos = position();
   1319                 long bookmark = getBookmark();
   1320                 long duration = duration();
   1321                 if ((pos < bookmark && (pos + 10000) > bookmark) ||
   1322                         (pos > bookmark && (pos - 10000) < bookmark)) {
   1323                     // The existing bookmark is close to the current
   1324                     // position, so don't update it.
   1325                     return;
   1326                 }
   1327                 if (pos < 15000 || (pos + 10000) > duration) {
   1328                     // if we're near the start or end, clear the bookmark
   1329                     pos = 0;
   1330                 }
   1331 
   1332                 // write 'pos' to the bookmark field
   1333                 ContentValues values = new ContentValues();
   1334                 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
   1335                 Uri uri = ContentUris.withAppendedId(
   1336                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
   1337                 getContentResolver().update(uri, values, null, null);
   1338             }
   1339         } catch (SQLiteException ex) {
   1340         }
   1341     }
   1342 
   1343     // Make sure there are at least 5 items after the currently playing item
   1344     // and no more than 10 items before.
   1345     private void doAutoShuffleUpdate() {
   1346         boolean notify = false;
   1347 
   1348         // remove old entries
   1349         if (mPlayPos > 10) {
   1350             removeTracks(0, mPlayPos - 9);
   1351             notify = true;
   1352         }
   1353         // add new entries if needed
   1354         int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
   1355         for (int i = 0; i < to_add; i++) {
   1356             // pick something at random from the list
   1357 
   1358             int lookback = mHistory.size();
   1359             int idx = -1;
   1360             while(true) {
   1361                 idx = mRand.nextInt(mAutoShuffleList.length);
   1362                 if (!wasRecentlyUsed(idx, lookback)) {
   1363                     break;
   1364                 }
   1365                 lookback /= 2;
   1366             }
   1367             mHistory.add(idx);
   1368             if (mHistory.size() > MAX_HISTORY_SIZE) {
   1369                 mHistory.remove(0);
   1370             }
   1371             ensurePlayListCapacity(mPlayListLen + 1);
   1372             mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
   1373             notify = true;
   1374         }
   1375         if (notify) {
   1376             notifyChange(QUEUE_CHANGED);
   1377         }
   1378     }
   1379 
   1380     // check that the specified idx is not in the history (but only look at at
   1381     // most lookbacksize entries in the history)
   1382     private boolean wasRecentlyUsed(int idx, int lookbacksize) {
   1383 
   1384         // early exit to prevent infinite loops in case idx == mPlayPos
   1385         if (lookbacksize == 0) {
   1386             return false;
   1387         }
   1388 
   1389         int histsize = mHistory.size();
   1390         if (histsize < lookbacksize) {
   1391             Log.d(LOGTAG, "lookback too big");
   1392             lookbacksize = histsize;
   1393         }
   1394         int maxidx = histsize - 1;
   1395         for (int i = 0; i < lookbacksize; i++) {
   1396             long entry = mHistory.get(maxidx - i);
   1397             if (entry == idx) {
   1398                 return true;
   1399             }
   1400         }
   1401         return false;
   1402     }
   1403 
   1404     // A simple variation of Random that makes sure that the
   1405     // value it returns is not equal to the value it returned
   1406     // previously, unless the interval is 1.
   1407     private static class Shuffler {
   1408         private int mPrevious;
   1409         private Random mRandom = new Random();
   1410         public int nextInt(int interval) {
   1411             int ret;
   1412             do {
   1413                 ret = mRandom.nextInt(interval);
   1414             } while (ret == mPrevious && interval > 1);
   1415             mPrevious = ret;
   1416             return ret;
   1417         }
   1418     };
   1419 
   1420     private boolean makeAutoShuffleList() {
   1421         ContentResolver res = getContentResolver();
   1422         Cursor c = null;
   1423         try {
   1424             c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
   1425                     new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
   1426                     null, null);
   1427             if (c == null || c.getCount() == 0) {
   1428                 return false;
   1429             }
   1430             int len = c.getCount();
   1431             long [] list = new long[len];
   1432             for (int i = 0; i < len; i++) {
   1433                 c.moveToNext();
   1434                 list[i] = c.getLong(0);
   1435             }
   1436             mAutoShuffleList = list;
   1437             return true;
   1438         } catch (RuntimeException ex) {
   1439         } finally {
   1440             if (c != null) {
   1441                 c.close();
   1442             }
   1443         }
   1444         return false;
   1445     }
   1446 
   1447     /**
   1448      * Removes the range of tracks specified from the play list. If a file within the range is
   1449      * the file currently being played, playback will move to the next file after the
   1450      * range.
   1451      * @param first The first file to be removed
   1452      * @param last The last file to be removed
   1453      * @return the number of tracks deleted
   1454      */
   1455     public int removeTracks(int first, int last) {
   1456         int numremoved = removeTracksInternal(first, last);
   1457         if (numremoved > 0) {
   1458             notifyChange(QUEUE_CHANGED);
   1459         }
   1460         return numremoved;
   1461     }
   1462 
   1463     private int removeTracksInternal(int first, int last) {
   1464         synchronized (this) {
   1465             if (last < first) return 0;
   1466             if (first < 0) first = 0;
   1467             if (last >= mPlayListLen) last = mPlayListLen - 1;
   1468 
   1469             boolean gotonext = false;
   1470             if (first <= mPlayPos && mPlayPos <= last) {
   1471                 mPlayPos = first;
   1472                 gotonext = true;
   1473             } else if (mPlayPos > last) {
   1474                 mPlayPos -= (last - first + 1);
   1475             }
   1476             int num = mPlayListLen - last - 1;
   1477             for (int i = 0; i < num; i++) {
   1478                 mPlayList[first + i] = mPlayList[last + 1 + i];
   1479             }
   1480             mPlayListLen -= last - first + 1;
   1481 
   1482             if (gotonext) {
   1483                 if (mPlayListLen == 0) {
   1484                     stop(true);
   1485                     mPlayPos = -1;
   1486                     if (mCursor != null) {
   1487                         mCursor.close();
   1488                         mCursor = null;
   1489                     }
   1490                 } else {
   1491                     if (mPlayPos >= mPlayListLen) {
   1492                         mPlayPos = 0;
   1493                     }
   1494                     boolean wasPlaying = isPlaying();
   1495                     stop(false);
   1496                     openCurrent();
   1497                     if (wasPlaying) {
   1498                         play();
   1499                     }
   1500                 }
   1501                 notifyChange(META_CHANGED);
   1502             }
   1503             return last - first + 1;
   1504         }
   1505     }
   1506 
   1507     /**
   1508      * Removes all instances of the track with the given id
   1509      * from the playlist.
   1510      * @param id The id to be removed
   1511      * @return how many instances of the track were removed
   1512      */
   1513     public int removeTrack(long id) {
   1514         int numremoved = 0;
   1515         synchronized (this) {
   1516             for (int i = 0; i < mPlayListLen; i++) {
   1517                 if (mPlayList[i] == id) {
   1518                     numremoved += removeTracksInternal(i, i);
   1519                     i--;
   1520                 }
   1521             }
   1522         }
   1523         if (numremoved > 0) {
   1524             notifyChange(QUEUE_CHANGED);
   1525         }
   1526         return numremoved;
   1527     }
   1528 
   1529     public void setShuffleMode(int shufflemode) {
   1530         synchronized(this) {
   1531             if (mShuffleMode == shufflemode && mPlayListLen > 0) {
   1532                 return;
   1533             }
   1534             mShuffleMode = shufflemode;
   1535             if (mShuffleMode == SHUFFLE_AUTO) {
   1536                 if (makeAutoShuffleList()) {
   1537                     mPlayListLen = 0;
   1538                     doAutoShuffleUpdate();
   1539                     mPlayPos = 0;
   1540                     openCurrent();
   1541                     play();
   1542                     notifyChange(META_CHANGED);
   1543                     return;
   1544                 } else {
   1545                     // failed to build a list of files to shuffle
   1546                     mShuffleMode = SHUFFLE_NONE;
   1547                 }
   1548             }
   1549             saveQueue(false);
   1550         }
   1551     }
   1552     public int getShuffleMode() {
   1553         return mShuffleMode;
   1554     }
   1555 
   1556     public void setRepeatMode(int repeatmode) {
   1557         synchronized(this) {
   1558             mRepeatMode = repeatmode;
   1559             saveQueue(false);
   1560         }
   1561     }
   1562     public int getRepeatMode() {
   1563         return mRepeatMode;
   1564     }
   1565 
   1566     public int getMediaMountedCount() {
   1567         return mMediaMountedCount;
   1568     }
   1569 
   1570     /**
   1571      * Returns the path of the currently playing file, or null if
   1572      * no file is currently playing.
   1573      */
   1574     public String getPath() {
   1575         return mFileToPlay;
   1576     }
   1577 
   1578     /**
   1579      * Returns the rowid of the currently playing file, or -1 if
   1580      * no file is currently playing.
   1581      */
   1582     public long getAudioId() {
   1583         synchronized (this) {
   1584             if (mPlayPos >= 0 && mPlayer.isInitialized()) {
   1585                 return mPlayList[mPlayPos];
   1586             }
   1587         }
   1588         return -1;
   1589     }
   1590 
   1591     /**
   1592      * Returns the position in the queue
   1593      * @return the position in the queue
   1594      */
   1595     public int getQueuePosition() {
   1596         synchronized(this) {
   1597             return mPlayPos;
   1598         }
   1599     }
   1600 
   1601     /**
   1602      * Starts playing the track at the given position in the queue.
   1603      * @param pos The position in the queue of the track that will be played.
   1604      */
   1605     public void setQueuePosition(int pos) {
   1606         synchronized(this) {
   1607             stop(false);
   1608             mPlayPos = pos;
   1609             openCurrent();
   1610             play();
   1611             notifyChange(META_CHANGED);
   1612             if (mShuffleMode == SHUFFLE_AUTO) {
   1613                 doAutoShuffleUpdate();
   1614             }
   1615         }
   1616     }
   1617 
   1618     public String getArtistName() {
   1619         synchronized(this) {
   1620             if (mCursor == null) {
   1621                 return null;
   1622             }
   1623             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
   1624         }
   1625     }
   1626 
   1627     public long getArtistId() {
   1628         synchronized (this) {
   1629             if (mCursor == null) {
   1630                 return -1;
   1631             }
   1632             return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
   1633         }
   1634     }
   1635 
   1636     public String getAlbumName() {
   1637         synchronized (this) {
   1638             if (mCursor == null) {
   1639                 return null;
   1640             }
   1641             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
   1642         }
   1643     }
   1644 
   1645     public long getAlbumId() {
   1646         synchronized (this) {
   1647             if (mCursor == null) {
   1648                 return -1;
   1649             }
   1650             return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
   1651         }
   1652     }
   1653 
   1654     public String getTrackName() {
   1655         synchronized (this) {
   1656             if (mCursor == null) {
   1657                 return null;
   1658             }
   1659             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
   1660         }
   1661     }
   1662 
   1663     private boolean isPodcast() {
   1664         synchronized (this) {
   1665             if (mCursor == null) {
   1666                 return false;
   1667             }
   1668             return (mCursor.getInt(PODCASTCOLIDX) > 0);
   1669         }
   1670     }
   1671 
   1672     private long getBookmark() {
   1673         synchronized (this) {
   1674             if (mCursor == null) {
   1675                 return 0;
   1676             }
   1677             return mCursor.getLong(BOOKMARKCOLIDX);
   1678         }
   1679     }
   1680 
   1681     /**
   1682      * Returns the duration of the file in milliseconds.
   1683      * Currently this method returns -1 for the duration of MIDI files.
   1684      */
   1685     public long duration() {
   1686         if (mPlayer.isInitialized()) {
   1687             return mPlayer.duration();
   1688         }
   1689         return -1;
   1690     }
   1691 
   1692     /**
   1693      * Returns the current playback position in milliseconds
   1694      */
   1695     public long position() {
   1696         if (mPlayer.isInitialized()) {
   1697             return mPlayer.position();
   1698         }
   1699         return -1;
   1700     }
   1701 
   1702     /**
   1703      * Seeks to the position specified.
   1704      *
   1705      * @param pos The position to seek to, in milliseconds
   1706      */
   1707     public long seek(long pos) {
   1708         if (mPlayer.isInitialized()) {
   1709             if (pos < 0) pos = 0;
   1710             if (pos > mPlayer.duration()) pos = mPlayer.duration();
   1711             return mPlayer.seek(pos);
   1712         }
   1713         return -1;
   1714     }
   1715 
   1716     /**
   1717      * Sets the audio session ID.
   1718      *
   1719      * @param sessionId: the audio session ID.
   1720      */
   1721     public void setAudioSessionId(int sessionId) {
   1722         synchronized (this) {
   1723             mPlayer.setAudioSessionId(sessionId);
   1724         }
   1725     }
   1726 
   1727     /**
   1728      * Returns the audio session ID.
   1729      */
   1730     public int getAudioSessionId() {
   1731         synchronized (this) {
   1732             return mPlayer.getAudioSessionId();
   1733         }
   1734     }
   1735 
   1736     /**
   1737      * Provides a unified interface for dealing with midi files and
   1738      * other media files.
   1739      */
   1740     private class MultiPlayer {
   1741         private MediaPlayer mMediaPlayer = new MediaPlayer();
   1742         private Handler mHandler;
   1743         private boolean mIsInitialized = false;
   1744 
   1745         public MultiPlayer() {
   1746             mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
   1747         }
   1748 
   1749         public void setDataSource(String path) {
   1750             try {
   1751                 mMediaPlayer.reset();
   1752                 mMediaPlayer.setOnPreparedListener(null);
   1753                 if (path.startsWith("content://")) {
   1754                     mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
   1755                 } else {
   1756                     mMediaPlayer.setDataSource(path);
   1757                 }
   1758                 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
   1759                 mMediaPlayer.prepare();
   1760             } catch (IOException ex) {
   1761                 // TODO: notify the user why the file couldn't be opened
   1762                 mIsInitialized = false;
   1763                 return;
   1764             } catch (IllegalArgumentException ex) {
   1765                 // TODO: notify the user why the file couldn't be opened
   1766                 mIsInitialized = false;
   1767                 return;
   1768             }
   1769             mMediaPlayer.setOnCompletionListener(listener);
   1770             mMediaPlayer.setOnErrorListener(errorListener);
   1771             Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
   1772             i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
   1773             i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
   1774             sendBroadcast(i);
   1775             mIsInitialized = true;
   1776         }
   1777 
   1778         public boolean isInitialized() {
   1779             return mIsInitialized;
   1780         }
   1781 
   1782         public void start() {
   1783             MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
   1784             mMediaPlayer.start();
   1785         }
   1786 
   1787         public void stop() {
   1788             mMediaPlayer.reset();
   1789             mIsInitialized = false;
   1790         }
   1791 
   1792         /**
   1793          * You CANNOT use this player anymore after calling release()
   1794          */
   1795         public void release() {
   1796             stop();
   1797             mMediaPlayer.release();
   1798         }
   1799 
   1800         public void pause() {
   1801             mMediaPlayer.pause();
   1802         }
   1803 
   1804         public void setHandler(Handler handler) {
   1805             mHandler = handler;
   1806         }
   1807 
   1808         MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
   1809             public void onCompletion(MediaPlayer mp) {
   1810                 // Acquire a temporary wakelock, since when we return from
   1811                 // this callback the MediaPlayer will release its wakelock
   1812                 // and allow the device to go to sleep.
   1813                 // This temporary wakelock is released when the RELEASE_WAKELOCK
   1814                 // message is processed, but just in case, put a timeout on it.
   1815                 mWakeLock.acquire(30000);
   1816                 mHandler.sendEmptyMessage(TRACK_ENDED);
   1817                 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
   1818             }
   1819         };
   1820 
   1821         MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
   1822             public boolean onError(MediaPlayer mp, int what, int extra) {
   1823                 switch (what) {
   1824                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
   1825                     mIsInitialized = false;
   1826                     mMediaPlayer.release();
   1827                     // Creating a new MediaPlayer and settings its wakemode does not
   1828                     // require the media service, so it's OK to do this now, while the
   1829                     // service is still being restarted
   1830                     mMediaPlayer = new MediaPlayer();
   1831                     mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
   1832                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
   1833                     return true;
   1834                 default:
   1835                     Log.d("MultiPlayer", "Error: " + what + "," + extra);
   1836                     break;
   1837                 }
   1838                 return false;
   1839            }
   1840         };
   1841 
   1842         public long duration() {
   1843             return mMediaPlayer.getDuration();
   1844         }
   1845 
   1846         public long position() {
   1847             return mMediaPlayer.getCurrentPosition();
   1848         }
   1849 
   1850         public long seek(long whereto) {
   1851             mMediaPlayer.seekTo((int) whereto);
   1852             return whereto;
   1853         }
   1854 
   1855         public void setVolume(float vol) {
   1856             mMediaPlayer.setVolume(vol, vol);
   1857         }
   1858 
   1859         public void setAudioSessionId(int sessionId) {
   1860             mMediaPlayer.setAudioSessionId(sessionId);
   1861         }
   1862 
   1863         public int getAudioSessionId() {
   1864             return mMediaPlayer.getAudioSessionId();
   1865         }
   1866     }
   1867 
   1868     /*
   1869      * By making this a static class with a WeakReference to the Service, we
   1870      * ensure that the Service can be GCd even when the system process still
   1871      * has a remote reference to the stub.
   1872      */
   1873     static class ServiceStub extends IMediaPlaybackService.Stub {
   1874         WeakReference<MediaPlaybackService> mService;
   1875 
   1876         ServiceStub(MediaPlaybackService service) {
   1877             mService = new WeakReference<MediaPlaybackService>(service);
   1878         }
   1879 
   1880         public void openFile(String path)
   1881         {
   1882             mService.get().open(path);
   1883         }
   1884         public void open(long [] list, int position) {
   1885             mService.get().open(list, position);
   1886         }
   1887         public int getQueuePosition() {
   1888             return mService.get().getQueuePosition();
   1889         }
   1890         public void setQueuePosition(int index) {
   1891             mService.get().setQueuePosition(index);
   1892         }
   1893         public boolean isPlaying() {
   1894             return mService.get().isPlaying();
   1895         }
   1896         public void stop() {
   1897             mService.get().stop();
   1898         }
   1899         public void pause() {
   1900             mService.get().pause();
   1901         }
   1902         public void play() {
   1903             mService.get().play();
   1904         }
   1905         public void prev() {
   1906             mService.get().prev();
   1907         }
   1908         public void next() {
   1909             mService.get().next(true);
   1910         }
   1911         public String getTrackName() {
   1912             return mService.get().getTrackName();
   1913         }
   1914         public String getAlbumName() {
   1915             return mService.get().getAlbumName();
   1916         }
   1917         public long getAlbumId() {
   1918             return mService.get().getAlbumId();
   1919         }
   1920         public String getArtistName() {
   1921             return mService.get().getArtistName();
   1922         }
   1923         public long getArtistId() {
   1924             return mService.get().getArtistId();
   1925         }
   1926         public void enqueue(long [] list , int action) {
   1927             mService.get().enqueue(list, action);
   1928         }
   1929         public long [] getQueue() {
   1930             return mService.get().getQueue();
   1931         }
   1932         public void moveQueueItem(int from, int to) {
   1933             mService.get().moveQueueItem(from, to);
   1934         }
   1935         public String getPath() {
   1936             return mService.get().getPath();
   1937         }
   1938         public long getAudioId() {
   1939             return mService.get().getAudioId();
   1940         }
   1941         public long position() {
   1942             return mService.get().position();
   1943         }
   1944         public long duration() {
   1945             return mService.get().duration();
   1946         }
   1947         public long seek(long pos) {
   1948             return mService.get().seek(pos);
   1949         }
   1950         public void setShuffleMode(int shufflemode) {
   1951             mService.get().setShuffleMode(shufflemode);
   1952         }
   1953         public int getShuffleMode() {
   1954             return mService.get().getShuffleMode();
   1955         }
   1956         public int removeTracks(int first, int last) {
   1957             return mService.get().removeTracks(first, last);
   1958         }
   1959         public int removeTrack(long id) {
   1960             return mService.get().removeTrack(id);
   1961         }
   1962         public void setRepeatMode(int repeatmode) {
   1963             mService.get().setRepeatMode(repeatmode);
   1964         }
   1965         public int getRepeatMode() {
   1966             return mService.get().getRepeatMode();
   1967         }
   1968         public int getMediaMountedCount() {
   1969             return mService.get().getMediaMountedCount();
   1970         }
   1971         public int getAudioSessionId() {
   1972             return mService.get().getAudioSessionId();
   1973         }
   1974     }
   1975 
   1976     @Override
   1977     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
   1978         writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
   1979         writer.println("Currently loaded:");
   1980         writer.println(getArtistName());
   1981         writer.println(getAlbumName());
   1982         writer.println(getTrackName());
   1983         writer.println(getPath());
   1984         writer.println("playing: " + mIsSupposedToBePlaying);
   1985         writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
   1986         writer.println("shuffle mode: " + mShuffleMode);
   1987         MusicUtils.debugDump(writer);
   1988     }
   1989 
   1990     private final IBinder mBinder = new ServiceStub(this);
   1991 }
   1992