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