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