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