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         c.moveToFirst();
   1011         return c;
   1012     }
   1013 
   1014     private void openCurrentAndNext() {
   1015         synchronized (this) {
   1016             if (mCursor != null) {
   1017                 mCursor.close();
   1018                 mCursor = null;
   1019             }
   1020 
   1021             if (mPlayListLen == 0) {
   1022                 return;
   1023             }
   1024             stop(false);
   1025 
   1026             mCursor = getCursorForId(mPlayList[mPlayPos]);
   1027             while(!open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + mCursor.getLong(IDCOLIDX))) {
   1028                 if (mOpenFailedCounter++ < 10 &&  mPlayListLen > 1) {
   1029                     int pos = getNextPosition(false);
   1030                     if (pos < 0) {
   1031                         gotoIdleState();
   1032                         if (mIsSupposedToBePlaying) {
   1033                             mIsSupposedToBePlaying = false;
   1034                             notifyChange(PLAYSTATE_CHANGED);
   1035                         }
   1036                         return;
   1037                     }
   1038                     mPlayPos = pos;
   1039                     stop(false);
   1040                     mPlayPos = pos;
   1041                     mCursor = getCursorForId(mPlayList[mPlayPos]);
   1042                 } else {
   1043                     mOpenFailedCounter = 0;
   1044                     if (!mQuietMode) {
   1045                         Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
   1046                     }
   1047                     Log.d(LOGTAG, "Failed to open file for playback");
   1048                     return;
   1049                 }
   1050             }
   1051 
   1052             // go to bookmark if needed
   1053             if (isPodcast()) {
   1054                 long bookmark = getBookmark();
   1055                 // Start playing a little bit before the bookmark,
   1056                 // so it's easier to get back in to the narrative.
   1057                 seek(bookmark - 5000);
   1058             }
   1059             setNextTrack();
   1060         }
   1061     }
   1062 
   1063     private void setNextTrack() {
   1064         mNextPlayPos = getNextPosition(false);
   1065         if (mNextPlayPos >= 0) {
   1066             long id = mPlayList[mNextPlayPos];
   1067             mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
   1068         }
   1069     }
   1070 
   1071     /**
   1072      * Opens the specified file and readies it for playback.
   1073      *
   1074      * @param path The full path of the file to be opened.
   1075      */
   1076     public boolean open(String path) {
   1077         synchronized (this) {
   1078             if (path == null) {
   1079                 return false;
   1080             }
   1081 
   1082             // if mCursor is null, try to associate path with a database cursor
   1083             if (mCursor == null) {
   1084 
   1085                 ContentResolver resolver = getContentResolver();
   1086                 Uri uri;
   1087                 String where;
   1088                 String selectionArgs[];
   1089                 if (path.startsWith("content://media/")) {
   1090                     uri = Uri.parse(path);
   1091                     where = null;
   1092                     selectionArgs = null;
   1093                 } else {
   1094                    uri = MediaStore.Audio.Media.getContentUriForPath(path);
   1095                    where = MediaStore.Audio.Media.DATA + "=?";
   1096                    selectionArgs = new String[] { path };
   1097                 }
   1098 
   1099                 try {
   1100                     mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
   1101                     if  (mCursor != null) {
   1102                         if (mCursor.getCount() == 0) {
   1103                             mCursor.close();
   1104                             mCursor = null;
   1105                         } else {
   1106                             mCursor.moveToNext();
   1107                             ensurePlayListCapacity(1);
   1108                             mPlayListLen = 1;
   1109                             mPlayList[0] = mCursor.getLong(IDCOLIDX);
   1110                             mPlayPos = 0;
   1111                         }
   1112                     }
   1113                 } catch (UnsupportedOperationException ex) {
   1114                 }
   1115             }
   1116             mFileToPlay = path;
   1117             mPlayer.setDataSource(mFileToPlay);
   1118             if (mPlayer.isInitialized()) {
   1119                 mOpenFailedCounter = 0;
   1120                 return true;
   1121             }
   1122             stop(true);
   1123             return false;
   1124         }
   1125     }
   1126 
   1127     /**
   1128      * Starts playback of a previously opened file.
   1129      */
   1130     public void play() {
   1131         mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
   1132                 AudioManager.AUDIOFOCUS_GAIN);
   1133         mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
   1134                 MediaButtonIntentReceiver.class.getName()));
   1135 
   1136         if (mPlayer.isInitialized()) {
   1137             // if we are at the end of the song, go to the next song first
   1138             long duration = mPlayer.duration();
   1139             if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
   1140                 mPlayer.position() >= duration - 2000) {
   1141                 gotoNext(true);
   1142             }
   1143 
   1144             mPlayer.start();
   1145             // make sure we fade in, in case a previous fadein was stopped because
   1146             // of another focus loss
   1147             mMediaplayerHandler.removeMessages(FADEDOWN);
   1148             mMediaplayerHandler.sendEmptyMessage(FADEUP);
   1149 
   1150             updateNotification();
   1151             if (!mIsSupposedToBePlaying) {
   1152                 mIsSupposedToBePlaying = true;
   1153                 notifyChange(PLAYSTATE_CHANGED);
   1154             }
   1155 
   1156         } else if (mPlayListLen <= 0) {
   1157             // This is mostly so that if you press 'play' on a bluetooth headset
   1158             // without every having played anything before, it will still play
   1159             // something.
   1160             setShuffleMode(SHUFFLE_AUTO);
   1161         }
   1162     }
   1163 
   1164     private void updateNotification() {
   1165         RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
   1166         views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
   1167         if (getAudioId() < 0) {
   1168             // streaming
   1169             views.setTextViewText(R.id.trackname, getPath());
   1170             views.setTextViewText(R.id.artistalbum, null);
   1171         } else {
   1172             String artist = getArtistName();
   1173             views.setTextViewText(R.id.trackname, getTrackName());
   1174             if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
   1175                 artist = getString(R.string.unknown_artist_name);
   1176             }
   1177             String album = getAlbumName();
   1178             if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
   1179                 album = getString(R.string.unknown_album_name);
   1180             }
   1181 
   1182             views.setTextViewText(R.id.artistalbum,
   1183                     getString(R.string.notification_artist_album, artist, album)
   1184                     );
   1185         }
   1186         Notification status = new Notification();
   1187         status.contentView = views;
   1188         status.flags |= Notification.FLAG_ONGOING_EVENT;
   1189         status.icon = R.drawable.stat_notify_musicplayer;
   1190         status.contentIntent = PendingIntent.getActivity(this, 0,
   1191                 new Intent("com.android.music.PLAYBACK_VIEWER")
   1192                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
   1193         startForeground(PLAYBACKSERVICE_STATUS, status);
   1194     }
   1195 
   1196     private void stop(boolean remove_status_icon) {
   1197         if (mPlayer.isInitialized()) {
   1198             mPlayer.stop();
   1199         }
   1200         mFileToPlay = null;
   1201         if (mCursor != null) {
   1202             mCursor.close();
   1203             mCursor = null;
   1204         }
   1205         if (remove_status_icon) {
   1206             gotoIdleState();
   1207         } else {
   1208             stopForeground(false);
   1209         }
   1210         if (remove_status_icon) {
   1211             mIsSupposedToBePlaying = false;
   1212         }
   1213     }
   1214 
   1215     /**
   1216      * Stops playback.
   1217      */
   1218     public void stop() {
   1219         stop(true);
   1220     }
   1221 
   1222     /**
   1223      * Pauses playback (call play() to resume)
   1224      */
   1225     public void pause() {
   1226         synchronized(this) {
   1227             mMediaplayerHandler.removeMessages(FADEUP);
   1228             if (isPlaying()) {
   1229                 mPlayer.pause();
   1230                 gotoIdleState();
   1231                 mIsSupposedToBePlaying = false;
   1232                 notifyChange(PLAYSTATE_CHANGED);
   1233                 saveBookmarkIfNeeded();
   1234             }
   1235         }
   1236     }
   1237 
   1238     /** Returns whether something is currently playing
   1239      *
   1240      * @return true if something is playing (or will be playing shortly, in case
   1241      * we're currently transitioning between tracks), false if not.
   1242      */
   1243     public boolean isPlaying() {
   1244         return mIsSupposedToBePlaying;
   1245     }
   1246 
   1247     /*
   1248       Desired behavior for prev/next/shuffle:
   1249 
   1250       - NEXT will move to the next track in the list when not shuffling, and to
   1251         a track randomly picked from the not-yet-played tracks when shuffling.
   1252         If all tracks have already been played, pick from the full set, but
   1253         avoid picking the previously played track if possible.
   1254       - when shuffling, PREV will go to the previously played track. Hitting PREV
   1255         again will go to the track played before that, etc. When the start of the
   1256         history has been reached, PREV is a no-op.
   1257         When not shuffling, PREV will go to the sequentially previous track (the
   1258         difference with the shuffle-case is mainly that when not shuffling, the
   1259         user can back up to tracks that are not in the history).
   1260 
   1261         Example:
   1262         When playing an album with 10 tracks from the start, and enabling shuffle
   1263         while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
   1264         the final play order might be 1-2-3-4-5-8-10-6-9-7.
   1265         When hitting 'prev' 8 times while playing track 7 in this example, the
   1266         user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
   1267         a random track will be picked again. If at any time user disables shuffling
   1268         the next/previous track will be picked in sequential order again.
   1269      */
   1270 
   1271     public void prev() {
   1272         synchronized (this) {
   1273             if (mShuffleMode == SHUFFLE_NORMAL) {
   1274                 // go to previously-played track and remove it from the history
   1275                 int histsize = mHistory.size();
   1276                 if (histsize == 0) {
   1277                     // prev is a no-op
   1278                     return;
   1279                 }
   1280                 Integer pos = mHistory.remove(histsize - 1);
   1281                 mPlayPos = pos.intValue();
   1282             } else {
   1283                 if (mPlayPos > 0) {
   1284                     mPlayPos--;
   1285                 } else {
   1286                     mPlayPos = mPlayListLen - 1;
   1287                 }
   1288             }
   1289             saveBookmarkIfNeeded();
   1290             stop(false);
   1291             openCurrentAndNext();
   1292             play();
   1293             notifyChange(META_CHANGED);
   1294         }
   1295     }
   1296 
   1297     /**
   1298      * Get the next position to play. Note that this may actually modify mPlayPos
   1299      * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
   1300      * be adjusted. Either way, the return value is the next value that should be
   1301      * assigned to mPlayPos;
   1302      */
   1303     private int getNextPosition(boolean force) {
   1304         if (mRepeatMode == REPEAT_CURRENT) {
   1305             if (mPlayPos < 0) return 0;
   1306             return mPlayPos;
   1307         } else if (mShuffleMode == SHUFFLE_NORMAL) {
   1308             // Pick random next track from the not-yet-played ones
   1309             // TODO: make it work right after adding/removing items in the queue.
   1310 
   1311             // Store the current file in the history, but keep the history at a
   1312             // reasonable size
   1313             if (mPlayPos >= 0) {
   1314                 mHistory.add(mPlayPos);
   1315             }
   1316             if (mHistory.size() > MAX_HISTORY_SIZE) {
   1317                 mHistory.removeElementAt(0);
   1318             }
   1319 
   1320             int numTracks = mPlayListLen;
   1321             int[] tracks = new int[numTracks];
   1322             for (int i=0;i < numTracks; i++) {
   1323                 tracks[i] = i;
   1324             }
   1325 
   1326             int numHistory = mHistory.size();
   1327             int numUnplayed = numTracks;
   1328             for (int i=0;i < numHistory; i++) {
   1329                 int idx = mHistory.get(i).intValue();
   1330                 if (idx < numTracks && tracks[idx] >= 0) {
   1331                     numUnplayed--;
   1332                     tracks[idx] = -1;
   1333                 }
   1334             }
   1335 
   1336             // 'numUnplayed' now indicates how many tracks have not yet
   1337             // been played, and 'tracks' contains the indices of those
   1338             // tracks.
   1339             if (numUnplayed <=0) {
   1340                 // everything's already been played
   1341                 if (mRepeatMode == REPEAT_ALL || force) {
   1342                     //pick from full set
   1343                     numUnplayed = numTracks;
   1344                     for (int i=0;i < numTracks; i++) {
   1345                         tracks[i] = i;
   1346                     }
   1347                 } else {
   1348                     // all done
   1349                     return -1;
   1350                 }
   1351             }
   1352             int skip = mRand.nextInt(numUnplayed);
   1353             int cnt = -1;
   1354             while (true) {
   1355                 while (tracks[++cnt] < 0)
   1356                     ;
   1357                 skip--;
   1358                 if (skip < 0) {
   1359                     break;
   1360                 }
   1361             }
   1362             return cnt;
   1363         } else if (mShuffleMode == SHUFFLE_AUTO) {
   1364             doAutoShuffleUpdate();
   1365             return mPlayPos + 1;
   1366         } else {
   1367             if (mPlayPos >= mPlayListLen - 1) {
   1368                 // we're at the end of the list
   1369                 if (mRepeatMode == REPEAT_NONE && !force) {
   1370                     // all done
   1371                     return -1;
   1372                 } else if (mRepeatMode == REPEAT_ALL || force) {
   1373                     return 0;
   1374                 }
   1375                 return -1;
   1376             } else {
   1377                 return mPlayPos + 1;
   1378             }
   1379         }
   1380     }
   1381 
   1382     public void gotoNext(boolean force) {
   1383         synchronized (this) {
   1384             if (mPlayListLen <= 0) {
   1385                 Log.d(LOGTAG, "No play queue");
   1386                 return;
   1387             }
   1388 
   1389             int pos = getNextPosition(force);
   1390             if (pos < 0) {
   1391                 gotoIdleState();
   1392                 if (mIsSupposedToBePlaying) {
   1393                     mIsSupposedToBePlaying = false;
   1394                     notifyChange(PLAYSTATE_CHANGED);
   1395                 }
   1396                 return;
   1397             }
   1398             mPlayPos = pos;
   1399             saveBookmarkIfNeeded();
   1400             stop(false);
   1401             mPlayPos = pos;
   1402             openCurrentAndNext();
   1403             play();
   1404             notifyChange(META_CHANGED);
   1405         }
   1406     }
   1407 
   1408     private void gotoIdleState() {
   1409         mDelayedStopHandler.removeCallbacksAndMessages(null);
   1410         Message msg = mDelayedStopHandler.obtainMessage();
   1411         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
   1412         stopForeground(true);
   1413     }
   1414 
   1415     private void saveBookmarkIfNeeded() {
   1416         try {
   1417             if (isPodcast()) {
   1418                 long pos = position();
   1419                 long bookmark = getBookmark();
   1420                 long duration = duration();
   1421                 if ((pos < bookmark && (pos + 10000) > bookmark) ||
   1422                         (pos > bookmark && (pos - 10000) < bookmark)) {
   1423                     // The existing bookmark is close to the current
   1424                     // position, so don't update it.
   1425                     return;
   1426                 }
   1427                 if (pos < 15000 || (pos + 10000) > duration) {
   1428                     // if we're near the start or end, clear the bookmark
   1429                     pos = 0;
   1430                 }
   1431 
   1432                 // write 'pos' to the bookmark field
   1433                 ContentValues values = new ContentValues();
   1434                 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
   1435                 Uri uri = ContentUris.withAppendedId(
   1436                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
   1437                 getContentResolver().update(uri, values, null, null);
   1438             }
   1439         } catch (SQLiteException ex) {
   1440         }
   1441     }
   1442 
   1443     // Make sure there are at least 5 items after the currently playing item
   1444     // and no more than 10 items before.
   1445     private void doAutoShuffleUpdate() {
   1446         boolean notify = false;
   1447 
   1448         // remove old entries
   1449         if (mPlayPos > 10) {
   1450             removeTracks(0, mPlayPos - 9);
   1451             notify = true;
   1452         }
   1453         // add new entries if needed
   1454         int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
   1455         for (int i = 0; i < to_add; i++) {
   1456             // pick something at random from the list
   1457 
   1458             int lookback = mHistory.size();
   1459             int idx = -1;
   1460             while(true) {
   1461                 idx = mRand.nextInt(mAutoShuffleList.length);
   1462                 if (!wasRecentlyUsed(idx, lookback)) {
   1463                     break;
   1464                 }
   1465                 lookback /= 2;
   1466             }
   1467             mHistory.add(idx);
   1468             if (mHistory.size() > MAX_HISTORY_SIZE) {
   1469                 mHistory.remove(0);
   1470             }
   1471             ensurePlayListCapacity(mPlayListLen + 1);
   1472             mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
   1473             notify = true;
   1474         }
   1475         if (notify) {
   1476             notifyChange(QUEUE_CHANGED);
   1477         }
   1478     }
   1479 
   1480     // check that the specified idx is not in the history (but only look at at
   1481     // most lookbacksize entries in the history)
   1482     private boolean wasRecentlyUsed(int idx, int lookbacksize) {
   1483 
   1484         // early exit to prevent infinite loops in case idx == mPlayPos
   1485         if (lookbacksize == 0) {
   1486             return false;
   1487         }
   1488 
   1489         int histsize = mHistory.size();
   1490         if (histsize < lookbacksize) {
   1491             Log.d(LOGTAG, "lookback too big");
   1492             lookbacksize = histsize;
   1493         }
   1494         int maxidx = histsize - 1;
   1495         for (int i = 0; i < lookbacksize; i++) {
   1496             long entry = mHistory.get(maxidx - i);
   1497             if (entry == idx) {
   1498                 return true;
   1499             }
   1500         }
   1501         return false;
   1502     }
   1503 
   1504     // A simple variation of Random that makes sure that the
   1505     // value it returns is not equal to the value it returned
   1506     // previously, unless the interval is 1.
   1507     private static class Shuffler {
   1508         private int mPrevious;
   1509         private Random mRandom = new Random();
   1510         public int nextInt(int interval) {
   1511             int ret;
   1512             do {
   1513                 ret = mRandom.nextInt(interval);
   1514             } while (ret == mPrevious && interval > 1);
   1515             mPrevious = ret;
   1516             return ret;
   1517         }
   1518     };
   1519 
   1520     private boolean makeAutoShuffleList() {
   1521         ContentResolver res = getContentResolver();
   1522         Cursor c = null;
   1523         try {
   1524             c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
   1525                     new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
   1526                     null, null);
   1527             if (c == null || c.getCount() == 0) {
   1528                 return false;
   1529             }
   1530             int len = c.getCount();
   1531             long [] list = new long[len];
   1532             for (int i = 0; i < len; i++) {
   1533                 c.moveToNext();
   1534                 list[i] = c.getLong(0);
   1535             }
   1536             mAutoShuffleList = list;
   1537             return true;
   1538         } catch (RuntimeException ex) {
   1539         } finally {
   1540             if (c != null) {
   1541                 c.close();
   1542             }
   1543         }
   1544         return false;
   1545     }
   1546 
   1547     /**
   1548      * Removes the range of tracks specified from the play list. If a file within the range is
   1549      * the file currently being played, playback will move to the next file after the
   1550      * range.
   1551      * @param first The first file to be removed
   1552      * @param last The last file to be removed
   1553      * @return the number of tracks deleted
   1554      */
   1555     public int removeTracks(int first, int last) {
   1556         int numremoved = removeTracksInternal(first, last);
   1557         if (numremoved > 0) {
   1558             notifyChange(QUEUE_CHANGED);
   1559         }
   1560         return numremoved;
   1561     }
   1562 
   1563     private int removeTracksInternal(int first, int last) {
   1564         synchronized (this) {
   1565             if (last < first) return 0;
   1566             if (first < 0) first = 0;
   1567             if (last >= mPlayListLen) last = mPlayListLen - 1;
   1568 
   1569             boolean gotonext = false;
   1570             if (first <= mPlayPos && mPlayPos <= last) {
   1571                 mPlayPos = first;
   1572                 gotonext = true;
   1573             } else if (mPlayPos > last) {
   1574                 mPlayPos -= (last - first + 1);
   1575             }
   1576             int num = mPlayListLen - last - 1;
   1577             for (int i = 0; i < num; i++) {
   1578                 mPlayList[first + i] = mPlayList[last + 1 + i];
   1579             }
   1580             mPlayListLen -= last - first + 1;
   1581 
   1582             if (gotonext) {
   1583                 if (mPlayListLen == 0) {
   1584                     stop(true);
   1585                     mPlayPos = -1;
   1586                     if (mCursor != null) {
   1587                         mCursor.close();
   1588                         mCursor = null;
   1589                     }
   1590                 } else {
   1591                     if (mPlayPos >= mPlayListLen) {
   1592                         mPlayPos = 0;
   1593                     }
   1594                     boolean wasPlaying = isPlaying();
   1595                     stop(false);
   1596                     openCurrentAndNext();
   1597                     if (wasPlaying) {
   1598                         play();
   1599                     }
   1600                 }
   1601                 notifyChange(META_CHANGED);
   1602             }
   1603             return last - first + 1;
   1604         }
   1605     }
   1606 
   1607     /**
   1608      * Removes all instances of the track with the given id
   1609      * from the playlist.
   1610      * @param id The id to be removed
   1611      * @return how many instances of the track were removed
   1612      */
   1613     public int removeTrack(long id) {
   1614         int numremoved = 0;
   1615         synchronized (this) {
   1616             for (int i = 0; i < mPlayListLen; i++) {
   1617                 if (mPlayList[i] == id) {
   1618                     numremoved += removeTracksInternal(i, i);
   1619                     i--;
   1620                 }
   1621             }
   1622         }
   1623         if (numremoved > 0) {
   1624             notifyChange(QUEUE_CHANGED);
   1625         }
   1626         return numremoved;
   1627     }
   1628 
   1629     public void setShuffleMode(int shufflemode) {
   1630         synchronized(this) {
   1631             if (mShuffleMode == shufflemode && mPlayListLen > 0) {
   1632                 return;
   1633             }
   1634             mShuffleMode = shufflemode;
   1635             if (mShuffleMode == SHUFFLE_AUTO) {
   1636                 if (makeAutoShuffleList()) {
   1637                     mPlayListLen = 0;
   1638                     doAutoShuffleUpdate();
   1639                     mPlayPos = 0;
   1640                     openCurrentAndNext();
   1641                     play();
   1642                     notifyChange(META_CHANGED);
   1643                     return;
   1644                 } else {
   1645                     // failed to build a list of files to shuffle
   1646                     mShuffleMode = SHUFFLE_NONE;
   1647                 }
   1648             }
   1649             saveQueue(false);
   1650         }
   1651     }
   1652     public int getShuffleMode() {
   1653         return mShuffleMode;
   1654     }
   1655 
   1656     public void setRepeatMode(int repeatmode) {
   1657         synchronized(this) {
   1658             mRepeatMode = repeatmode;
   1659             setNextTrack();
   1660             saveQueue(false);
   1661         }
   1662     }
   1663     public int getRepeatMode() {
   1664         return mRepeatMode;
   1665     }
   1666 
   1667     public int getMediaMountedCount() {
   1668         return mMediaMountedCount;
   1669     }
   1670 
   1671     /**
   1672      * Returns the path of the currently playing file, or null if
   1673      * no file is currently playing.
   1674      */
   1675     public String getPath() {
   1676         return mFileToPlay;
   1677     }
   1678 
   1679     /**
   1680      * Returns the rowid of the currently playing file, or -1 if
   1681      * no file is currently playing.
   1682      */
   1683     public long getAudioId() {
   1684         synchronized (this) {
   1685             if (mPlayPos >= 0 && mPlayer.isInitialized()) {
   1686                 return mPlayList[mPlayPos];
   1687             }
   1688         }
   1689         return -1;
   1690     }
   1691 
   1692     /**
   1693      * Returns the position in the queue
   1694      * @return the position in the queue
   1695      */
   1696     public int getQueuePosition() {
   1697         synchronized(this) {
   1698             return mPlayPos;
   1699         }
   1700     }
   1701 
   1702     /**
   1703      * Starts playing the track at the given position in the queue.
   1704      * @param pos The position in the queue of the track that will be played.
   1705      */
   1706     public void setQueuePosition(int pos) {
   1707         synchronized(this) {
   1708             stop(false);
   1709             mPlayPos = pos;
   1710             openCurrentAndNext();
   1711             play();
   1712             notifyChange(META_CHANGED);
   1713             if (mShuffleMode == SHUFFLE_AUTO) {
   1714                 doAutoShuffleUpdate();
   1715             }
   1716         }
   1717     }
   1718 
   1719     public String getArtistName() {
   1720         synchronized(this) {
   1721             if (mCursor == null) {
   1722                 return null;
   1723             }
   1724             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
   1725         }
   1726     }
   1727 
   1728     public long getArtistId() {
   1729         synchronized (this) {
   1730             if (mCursor == null) {
   1731                 return -1;
   1732             }
   1733             return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
   1734         }
   1735     }
   1736 
   1737     public String getAlbumName() {
   1738         synchronized (this) {
   1739             if (mCursor == null) {
   1740                 return null;
   1741             }
   1742             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
   1743         }
   1744     }
   1745 
   1746     public long getAlbumId() {
   1747         synchronized (this) {
   1748             if (mCursor == null) {
   1749                 return -1;
   1750             }
   1751             return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
   1752         }
   1753     }
   1754 
   1755     public String getTrackName() {
   1756         synchronized (this) {
   1757             if (mCursor == null) {
   1758                 return null;
   1759             }
   1760             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
   1761         }
   1762     }
   1763 
   1764     private boolean isPodcast() {
   1765         synchronized (this) {
   1766             if (mCursor == null) {
   1767                 return false;
   1768             }
   1769             return (mCursor.getInt(PODCASTCOLIDX) > 0);
   1770         }
   1771     }
   1772 
   1773     private long getBookmark() {
   1774         synchronized (this) {
   1775             if (mCursor == null) {
   1776                 return 0;
   1777             }
   1778             return mCursor.getLong(BOOKMARKCOLIDX);
   1779         }
   1780     }
   1781 
   1782     /**
   1783      * Returns the duration of the file in milliseconds.
   1784      * Currently this method returns -1 for the duration of MIDI files.
   1785      */
   1786     public long duration() {
   1787         if (mPlayer.isInitialized()) {
   1788             return mPlayer.duration();
   1789         }
   1790         return -1;
   1791     }
   1792 
   1793     /**
   1794      * Returns the current playback position in milliseconds
   1795      */
   1796     public long position() {
   1797         if (mPlayer.isInitialized()) {
   1798             return mPlayer.position();
   1799         }
   1800         return -1;
   1801     }
   1802 
   1803     /**
   1804      * Seeks to the position specified.
   1805      *
   1806      * @param pos The position to seek to, in milliseconds
   1807      */
   1808     public long seek(long pos) {
   1809         if (mPlayer.isInitialized()) {
   1810             if (pos < 0) pos = 0;
   1811             if (pos > mPlayer.duration()) pos = mPlayer.duration();
   1812             return mPlayer.seek(pos);
   1813         }
   1814         return -1;
   1815     }
   1816 
   1817     /**
   1818      * Sets the audio session ID.
   1819      *
   1820      * @param sessionId: the audio session ID.
   1821      */
   1822     public void setAudioSessionId(int sessionId) {
   1823         synchronized (this) {
   1824             mPlayer.setAudioSessionId(sessionId);
   1825         }
   1826     }
   1827 
   1828     /**
   1829      * Returns the audio session ID.
   1830      */
   1831     public int getAudioSessionId() {
   1832         synchronized (this) {
   1833             return mPlayer.getAudioSessionId();
   1834         }
   1835     }
   1836 
   1837     /**
   1838      * Provides a unified interface for dealing with midi files and
   1839      * other media files.
   1840      */
   1841     private class MultiPlayer {
   1842         private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
   1843         private CompatMediaPlayer mNextMediaPlayer;
   1844         private Handler mHandler;
   1845         private boolean mIsInitialized = false;
   1846 
   1847         public MultiPlayer() {
   1848             mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
   1849         }
   1850 
   1851         public void setDataSource(String path) {
   1852             mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
   1853             if (mIsInitialized) {
   1854                 setNextDataSource(null);
   1855             }
   1856         }
   1857 
   1858         private boolean setDataSourceImpl(MediaPlayer player, String path) {
   1859             try {
   1860                 player.reset();
   1861                 player.setOnPreparedListener(null);
   1862                 if (path.startsWith("content://")) {
   1863                     player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
   1864                 } else {
   1865                     player.setDataSource(path);
   1866                 }
   1867                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
   1868                 player.prepare();
   1869             } catch (IOException ex) {
   1870                 // TODO: notify the user why the file couldn't be opened
   1871                 return false;
   1872             } catch (IllegalArgumentException ex) {
   1873                 // TODO: notify the user why the file couldn't be opened
   1874                 return false;
   1875             }
   1876             player.setOnCompletionListener(listener);
   1877             player.setOnErrorListener(errorListener);
   1878             Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
   1879             i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
   1880             i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
   1881             sendBroadcast(i);
   1882             return true;
   1883         }
   1884 
   1885         public void setNextDataSource(String path) {
   1886             mCurrentMediaPlayer.setNextMediaPlayer(null);
   1887             if (mNextMediaPlayer != null) {
   1888                 mNextMediaPlayer.release();
   1889                 mNextMediaPlayer = null;
   1890             }
   1891             if (path == null) {
   1892                 return;
   1893             }
   1894             mNextMediaPlayer = new CompatMediaPlayer();
   1895             mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
   1896             mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
   1897             if (setDataSourceImpl(mNextMediaPlayer, path)) {
   1898                 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
   1899             } else {
   1900                 // failed to open next, we'll transition the old fashioned way,
   1901                 // which will skip over the faulty file
   1902                 mNextMediaPlayer.release();
   1903                 mNextMediaPlayer = null;
   1904             }
   1905         }
   1906 
   1907         public boolean isInitialized() {
   1908             return mIsInitialized;
   1909         }
   1910 
   1911         public void start() {
   1912             MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
   1913             mCurrentMediaPlayer.start();
   1914         }
   1915 
   1916         public void stop() {
   1917             mCurrentMediaPlayer.reset();
   1918             mIsInitialized = false;
   1919         }
   1920 
   1921         /**
   1922          * You CANNOT use this player anymore after calling release()
   1923          */
   1924         public void release() {
   1925             stop();
   1926             mCurrentMediaPlayer.release();
   1927         }
   1928 
   1929         public void pause() {
   1930             mCurrentMediaPlayer.pause();
   1931         }
   1932 
   1933         public void setHandler(Handler handler) {
   1934             mHandler = handler;
   1935         }
   1936 
   1937         MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
   1938             public void onCompletion(MediaPlayer mp) {
   1939                 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
   1940                     mCurrentMediaPlayer.release();
   1941                     mCurrentMediaPlayer = mNextMediaPlayer;
   1942                     mNextMediaPlayer = null;
   1943                     mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
   1944                 } else {
   1945                     // Acquire a temporary wakelock, since when we return from
   1946                     // this callback the MediaPlayer will release its wakelock
   1947                     // and allow the device to go to sleep.
   1948                     // This temporary wakelock is released when the RELEASE_WAKELOCK
   1949                     // message is processed, but just in case, put a timeout on it.
   1950                     mWakeLock.acquire(30000);
   1951                     mHandler.sendEmptyMessage(TRACK_ENDED);
   1952                     mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
   1953                 }
   1954             }
   1955         };
   1956 
   1957         MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
   1958             public boolean onError(MediaPlayer mp, int what, int extra) {
   1959                 switch (what) {
   1960                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
   1961                     mIsInitialized = false;
   1962                     mCurrentMediaPlayer.release();
   1963                     // Creating a new MediaPlayer and settings its wakemode does not
   1964                     // require the media service, so it's OK to do this now, while the
   1965                     // service is still being restarted
   1966                     mCurrentMediaPlayer = new CompatMediaPlayer();
   1967                     mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
   1968                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
   1969                     return true;
   1970                 default:
   1971                     Log.d("MultiPlayer", "Error: " + what + "," + extra);
   1972                     break;
   1973                 }
   1974                 return false;
   1975            }
   1976         };
   1977 
   1978         public long duration() {
   1979             return mCurrentMediaPlayer.getDuration();
   1980         }
   1981 
   1982         public long position() {
   1983             return mCurrentMediaPlayer.getCurrentPosition();
   1984         }
   1985 
   1986         public long seek(long whereto) {
   1987             mCurrentMediaPlayer.seekTo((int) whereto);
   1988             return whereto;
   1989         }
   1990 
   1991         public void setVolume(float vol) {
   1992             mCurrentMediaPlayer.setVolume(vol, vol);
   1993         }
   1994 
   1995         public void setAudioSessionId(int sessionId) {
   1996             mCurrentMediaPlayer.setAudioSessionId(sessionId);
   1997         }
   1998 
   1999         public int getAudioSessionId() {
   2000             return mCurrentMediaPlayer.getAudioSessionId();
   2001         }
   2002     }
   2003 
   2004     static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
   2005 
   2006         private boolean mCompatMode = true;
   2007         private MediaPlayer mNextPlayer;
   2008         private OnCompletionListener mCompletion;
   2009 
   2010         public CompatMediaPlayer() {
   2011             try {
   2012                 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
   2013                 mCompatMode = false;
   2014             } catch (NoSuchMethodException e) {
   2015                 mCompatMode = true;
   2016                 super.setOnCompletionListener(this);
   2017             }
   2018         }
   2019 
   2020         public void setNextMediaPlayer(MediaPlayer next) {
   2021             if (mCompatMode) {
   2022                 mNextPlayer = next;
   2023             } else {
   2024                 super.setNextMediaPlayer(next);
   2025             }
   2026         }
   2027 
   2028         @Override
   2029         public void setOnCompletionListener(OnCompletionListener listener) {
   2030             if (mCompatMode) {
   2031                 mCompletion = listener;
   2032             } else {
   2033                 super.setOnCompletionListener(listener);
   2034             }
   2035         }
   2036 
   2037         @Override
   2038         public void onCompletion(MediaPlayer mp) {
   2039             if (mNextPlayer != null) {
   2040                 // as it turns out, starting a new MediaPlayer on the completion
   2041                 // of a previous player ends up slightly overlapping the two
   2042                 // playbacks, so slightly delaying the start of the next player
   2043                 // gives a better user experience
   2044                 SystemClock.sleep(50);
   2045                 mNextPlayer.start();
   2046             }
   2047             mCompletion.onCompletion(this);
   2048         }
   2049     }
   2050 
   2051     /*
   2052      * By making this a static class with a WeakReference to the Service, we
   2053      * ensure that the Service can be GCd even when the system process still
   2054      * has a remote reference to the stub.
   2055      */
   2056     static class ServiceStub extends IMediaPlaybackService.Stub {
   2057         WeakReference<MediaPlaybackService> mService;
   2058 
   2059         ServiceStub(MediaPlaybackService service) {
   2060             mService = new WeakReference<MediaPlaybackService>(service);
   2061         }
   2062 
   2063         public void openFile(String path)
   2064         {
   2065             mService.get().open(path);
   2066         }
   2067         public void open(long [] list, int position) {
   2068             mService.get().open(list, position);
   2069         }
   2070         public int getQueuePosition() {
   2071             return mService.get().getQueuePosition();
   2072         }
   2073         public void setQueuePosition(int index) {
   2074             mService.get().setQueuePosition(index);
   2075         }
   2076         public boolean isPlaying() {
   2077             return mService.get().isPlaying();
   2078         }
   2079         public void stop() {
   2080             mService.get().stop();
   2081         }
   2082         public void pause() {
   2083             mService.get().pause();
   2084         }
   2085         public void play() {
   2086             mService.get().play();
   2087         }
   2088         public void prev() {
   2089             mService.get().prev();
   2090         }
   2091         public void next() {
   2092             mService.get().gotoNext(true);
   2093         }
   2094         public String getTrackName() {
   2095             return mService.get().getTrackName();
   2096         }
   2097         public String getAlbumName() {
   2098             return mService.get().getAlbumName();
   2099         }
   2100         public long getAlbumId() {
   2101             return mService.get().getAlbumId();
   2102         }
   2103         public String getArtistName() {
   2104             return mService.get().getArtistName();
   2105         }
   2106         public long getArtistId() {
   2107             return mService.get().getArtistId();
   2108         }
   2109         public void enqueue(long [] list , int action) {
   2110             mService.get().enqueue(list, action);
   2111         }
   2112         public long [] getQueue() {
   2113             return mService.get().getQueue();
   2114         }
   2115         public void moveQueueItem(int from, int to) {
   2116             mService.get().moveQueueItem(from, to);
   2117         }
   2118         public String getPath() {
   2119             return mService.get().getPath();
   2120         }
   2121         public long getAudioId() {
   2122             return mService.get().getAudioId();
   2123         }
   2124         public long position() {
   2125             return mService.get().position();
   2126         }
   2127         public long duration() {
   2128             return mService.get().duration();
   2129         }
   2130         public long seek(long pos) {
   2131             return mService.get().seek(pos);
   2132         }
   2133         public void setShuffleMode(int shufflemode) {
   2134             mService.get().setShuffleMode(shufflemode);
   2135         }
   2136         public int getShuffleMode() {
   2137             return mService.get().getShuffleMode();
   2138         }
   2139         public int removeTracks(int first, int last) {
   2140             return mService.get().removeTracks(first, last);
   2141         }
   2142         public int removeTrack(long id) {
   2143             return mService.get().removeTrack(id);
   2144         }
   2145         public void setRepeatMode(int repeatmode) {
   2146             mService.get().setRepeatMode(repeatmode);
   2147         }
   2148         public int getRepeatMode() {
   2149             return mService.get().getRepeatMode();
   2150         }
   2151         public int getMediaMountedCount() {
   2152             return mService.get().getMediaMountedCount();
   2153         }
   2154         public int getAudioSessionId() {
   2155             return mService.get().getAudioSessionId();
   2156         }
   2157     }
   2158 
   2159     @Override
   2160     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
   2161         writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
   2162         writer.println("Currently loaded:");
   2163         writer.println(getArtistName());
   2164         writer.println(getAlbumName());
   2165         writer.println(getTrackName());
   2166         writer.println(getPath());
   2167         writer.println("playing: " + mIsSupposedToBePlaying);
   2168         writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
   2169         writer.println("shuffle mode: " + mShuffleMode);
   2170         MusicUtils.debugDump(writer);
   2171     }
   2172 
   2173     private final IBinder mBinder = new ServiceStub(this);
   2174 }
   2175