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