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