PageRenderTime 1810ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/android/music/MediaPlaybackService.java

https://github.com/CyanogenMod/android_packages_apps_Music
Java | 2071 lines | 1598 code | 161 blank | 312 comment | 371 complexity | 055719bf0797c5a251d61a66b1d1342e MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  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. package com.android.music;
  17. import android.app.Notification;
  18. import android.app.NotificationManager;
  19. import android.app.PendingIntent;
  20. import android.app.Service;
  21. import android.appwidget.AppWidgetManager;
  22. import android.content.BroadcastReceiver;
  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.SharedPreferences;
  31. import android.content.SharedPreferences.Editor;
  32. import android.database.Cursor;
  33. import android.database.sqlite.SQLiteException;
  34. import android.graphics.Bitmap;
  35. import android.media.AudioManager;
  36. import android.media.AudioManager.OnAudioFocusChangeListener;
  37. import android.media.MediaMetadataRetriever;
  38. import android.media.MediaPlayer;
  39. import android.media.RemoteControlClient;
  40. import android.media.RemoteControlClient.MetadataEditor;
  41. import android.media.audiofx.AudioEffect;
  42. import android.net.Uri;
  43. import android.os.Handler;
  44. import android.os.IBinder;
  45. import android.os.Message;
  46. import android.os.PowerManager;
  47. import android.os.PowerManager.WakeLock;
  48. import android.os.SystemClock;
  49. import android.provider.MediaStore;
  50. import android.util.Log;
  51. import android.view.KeyEvent;
  52. import android.widget.RemoteViews;
  53. import android.widget.Toast;
  54. import java.io.FileDescriptor;
  55. import java.io.IOException;
  56. import java.io.PrintWriter;
  57. import java.lang.ref.WeakReference;
  58. import java.util.Random;
  59. import java.util.Vector;
  60. /**
  61. * Provides "background" audio playback capabilities, allowing the
  62. * user to switch between activities without stopping playback.
  63. */
  64. public class MediaPlaybackService extends Service {
  65. /** used to specify whether enqueue() should start playing
  66. * the new list of files right away, next or once all the currently
  67. * queued files have been played
  68. */
  69. public static final int NOW = 1;
  70. public static final int NEXT = 2;
  71. public static final int LAST = 3;
  72. public static final int PLAYBACKSERVICE_STATUS = 1;
  73. public static final int SHUFFLE_NONE = 0;
  74. public static final int SHUFFLE_NORMAL = 1;
  75. public static final int SHUFFLE_AUTO = 2;
  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. public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
  80. public static final String META_CHANGED = "com.android.music.metachanged";
  81. public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
  82. public static final String SERVICECMD = "com.android.music.musicservicecommand";
  83. public static final String CMDNAME = "command";
  84. public static final String CMDTOGGLEPAUSE = "togglepause";
  85. public static final String CMDSTOP = "stop";
  86. public static final String CMDPAUSE = "pause";
  87. public static final String CMDPLAY = "play";
  88. public static final String CMDPREVIOUS = "previous";
  89. public static final String CMDNEXT = "next";
  90. public static final String CMDNOTIF = "buttonId";
  91. public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
  92. public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
  93. public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
  94. public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
  95. private static final int TRACK_ENDED = 1;
  96. private static final int RELEASE_WAKELOCK = 2;
  97. private static final int SERVER_DIED = 3;
  98. private static final int FOCUSCHANGE = 4;
  99. private static final int FADEDOWN = 5;
  100. private static final int FADEUP = 6;
  101. private static final int MAX_HISTORY_SIZE = 100;
  102. private Notification status;
  103. private MultiPlayer mPlayer;
  104. private String mFileToPlay;
  105. private int mShuffleMode = SHUFFLE_NONE;
  106. private int mRepeatMode = REPEAT_NONE;
  107. private int mMediaMountedCount = 0;
  108. private long [] mAutoShuffleList = null;
  109. private long [] mPlayList = null;
  110. private int mPlayListLen = 0;
  111. private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
  112. private Cursor mCursor;
  113. private int mPlayPos = -1;
  114. private static final String LOGTAG = "MediaPlaybackService";
  115. private final Shuffler mRand = new Shuffler();
  116. private int mOpenFailedCounter = 0;
  117. String[] mCursorCols = new String[] {
  118. "audio._id AS _id", // index must match IDCOLIDX below
  119. MediaStore.Audio.Media.ARTIST,
  120. MediaStore.Audio.Media.ALBUM,
  121. MediaStore.Audio.Media.TITLE,
  122. MediaStore.Audio.Media.DATA,
  123. MediaStore.Audio.Media.MIME_TYPE,
  124. MediaStore.Audio.Media.ALBUM_ID,
  125. MediaStore.Audio.Media.ARTIST_ID,
  126. MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
  127. MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
  128. };
  129. private final static int IDCOLIDX = 0;
  130. private final static int PODCASTCOLIDX = 8;
  131. private final static int BOOKMARKCOLIDX = 9;
  132. private BroadcastReceiver mUnmountReceiver = null;
  133. private WakeLock mWakeLock;
  134. private int mServiceStartId = -1;
  135. private boolean mServiceInUse = false;
  136. private boolean mIsSupposedToBePlaying = false;
  137. private boolean mQuietMode = false;
  138. private AudioManager mAudioManager;
  139. private boolean mQueueIsSaveable = true;
  140. // used to track what type of audio focus loss caused the playback to pause
  141. private boolean mPausedByTransientLossOfFocus = false;
  142. private SharedPreferences mPreferences;
  143. // We use this to distinguish between different cards when saving/restoring playlists.
  144. // This will have to change if we want to support multiple simultaneous cards.
  145. private int mCardId;
  146. private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
  147. // interval after which we stop the service when idle
  148. private static final int IDLE_DELAY = 60000;
  149. private RemoteControlClient mRemoteControlClient;
  150. private Handler mMediaplayerHandler = new Handler() {
  151. float mCurrentVolume = 1.0f;
  152. @Override
  153. public void handleMessage(Message msg) {
  154. MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
  155. switch (msg.what) {
  156. case FADEDOWN:
  157. mCurrentVolume -= .05f;
  158. if (mCurrentVolume > .2f) {
  159. mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
  160. } else {
  161. mCurrentVolume = .2f;
  162. }
  163. mPlayer.setVolume(mCurrentVolume);
  164. break;
  165. case FADEUP:
  166. mCurrentVolume += .01f;
  167. if (mCurrentVolume < 1.0f) {
  168. mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
  169. } else {
  170. mCurrentVolume = 1.0f;
  171. }
  172. mPlayer.setVolume(mCurrentVolume);
  173. break;
  174. case SERVER_DIED:
  175. if (mIsSupposedToBePlaying) {
  176. next(true);
  177. } else {
  178. // the server died when we were idle, so just
  179. // reopen the same song (it will start again
  180. // from the beginning though when the user
  181. // restarts)
  182. openCurrent();
  183. }
  184. break;
  185. case TRACK_ENDED:
  186. if (mRepeatMode == REPEAT_CURRENT) {
  187. seek(0);
  188. play();
  189. } else {
  190. next(false);
  191. }
  192. break;
  193. case RELEASE_WAKELOCK:
  194. mWakeLock.release();
  195. break;
  196. case FOCUSCHANGE:
  197. // This code is here so we can better synchronize it with the code that
  198. // handles fade-in
  199. switch (msg.arg1) {
  200. case AudioManager.AUDIOFOCUS_LOSS:
  201. Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
  202. if(isPlaying()) {
  203. mPausedByTransientLossOfFocus = false;
  204. }
  205. pause();
  206. break;
  207. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
  208. mMediaplayerHandler.removeMessages(FADEUP);
  209. mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
  210. break;
  211. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
  212. Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
  213. if(isPlaying()) {
  214. mPausedByTransientLossOfFocus = true;
  215. }
  216. pause();
  217. break;
  218. case AudioManager.AUDIOFOCUS_GAIN:
  219. Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
  220. if(!isPlaying() && mPausedByTransientLossOfFocus) {
  221. mPausedByTransientLossOfFocus = false;
  222. mCurrentVolume = 0f;
  223. mPlayer.setVolume(mCurrentVolume);
  224. play(); // also queues a fade-in
  225. } else {
  226. mMediaplayerHandler.removeMessages(FADEDOWN);
  227. mMediaplayerHandler.sendEmptyMessage(FADEUP);
  228. }
  229. break;
  230. default:
  231. Log.e(LOGTAG, "Unknown audio focus change code");
  232. }
  233. break;
  234. default:
  235. break;
  236. }
  237. }
  238. };
  239. private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
  240. @Override
  241. public void onReceive(Context context, Intent intent) {
  242. String action = intent.getAction();
  243. String cmd = intent.getStringExtra("command");
  244. MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
  245. if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
  246. next(true);
  247. } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
  248. prev();
  249. } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
  250. if (isPlaying()) {
  251. pause();
  252. mPausedByTransientLossOfFocus = false;
  253. } else {
  254. play();
  255. }
  256. } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
  257. pause();
  258. mPausedByTransientLossOfFocus = false;
  259. } else if (CMDPLAY.equals(cmd)) {
  260. play();
  261. } else if (CMDSTOP.equals(cmd)) {
  262. pause();
  263. mPausedByTransientLossOfFocus = false;
  264. seek(0);
  265. } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
  266. // Someone asked us to refresh a set of specific widgets, probably
  267. // because they were just added.
  268. int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
  269. mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
  270. }
  271. }
  272. };
  273. private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
  274. public void onAudioFocusChange(int focusChange) {
  275. mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
  276. }
  277. };
  278. public MediaPlaybackService() {
  279. }
  280. @Override
  281. public void onCreate() {
  282. super.onCreate();
  283. mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
  284. ComponentName rec = new ComponentName(getPackageName(),
  285. MediaButtonIntentReceiver.class.getName());
  286. mAudioManager.registerMediaButtonEventReceiver(rec);
  287. Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
  288. mediaButtonIntent.setComponent(rec);
  289. PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
  290. 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  291. mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
  292. mAudioManager.registerRemoteControlClient(mRemoteControlClient);
  293. int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
  294. | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
  295. | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
  296. | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
  297. | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
  298. | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
  299. mRemoteControlClient.setTransportControlFlags(flags);
  300. mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
  301. mCardId = MusicUtils.getCardId(this);
  302. registerExternalStorageListener();
  303. // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
  304. mPlayer = new MultiPlayer();
  305. mPlayer.setHandler(mMediaplayerHandler);
  306. reloadQueue();
  307. notifyChange(QUEUE_CHANGED);
  308. notifyChange(META_CHANGED);
  309. IntentFilter commandFilter = new IntentFilter();
  310. commandFilter.addAction(SERVICECMD);
  311. commandFilter.addAction(TOGGLEPAUSE_ACTION);
  312. commandFilter.addAction(PAUSE_ACTION);
  313. commandFilter.addAction(NEXT_ACTION);
  314. commandFilter.addAction(PREVIOUS_ACTION);
  315. registerReceiver(mIntentReceiver, commandFilter);
  316. PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
  317. mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
  318. mWakeLock.setReferenceCounted(false);
  319. // If the service was idle, but got killed before it stopped itself, the
  320. // system will relaunch it. Make sure it gets stopped again in that case.
  321. Message msg = mDelayedStopHandler.obtainMessage();
  322. mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
  323. }
  324. @Override
  325. public void onDestroy() {
  326. // Check that we're not being destroyed while something is still playing.
  327. if (isPlaying()) {
  328. Log.e(LOGTAG, "Service being destroyed while still playing.");
  329. }
  330. // release all MediaPlayer resources, including the native player and wakelocks
  331. Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
  332. i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
  333. i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
  334. sendBroadcast(i);
  335. mPlayer.release();
  336. mPlayer = null;
  337. mAudioManager.abandonAudioFocus(mAudioFocusListener);
  338. mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
  339. // make sure there aren't any other messages coming
  340. mDelayedStopHandler.removeCallbacksAndMessages(null);
  341. mMediaplayerHandler.removeCallbacksAndMessages(null);
  342. if (mCursor != null) {
  343. mCursor.close();
  344. mCursor = null;
  345. }
  346. unregisterReceiver(mIntentReceiver);
  347. if (mUnmountReceiver != null) {
  348. unregisterReceiver(mUnmountReceiver);
  349. mUnmountReceiver = null;
  350. }
  351. mWakeLock.release();
  352. super.onDestroy();
  353. }
  354. private final char hexdigits [] = new char [] {
  355. '0', '1', '2', '3',
  356. '4', '5', '6', '7',
  357. '8', '9', 'a', 'b',
  358. 'c', 'd', 'e', 'f'
  359. };
  360. private void saveQueue(boolean full) {
  361. if (!mQueueIsSaveable) {
  362. return;
  363. }
  364. Editor ed = mPreferences.edit();
  365. //long start = System.currentTimeMillis();
  366. if (full) {
  367. StringBuilder q = new StringBuilder();
  368. // The current playlist is saved as a list of "reverse hexadecimal"
  369. // numbers, which we can generate faster than normal decimal or
  370. // hexadecimal numbers, which in turn allows us to save the playlist
  371. // more often without worrying too much about performance.
  372. // (saving the full state takes about 40 ms under no-load conditions
  373. // on the phone)
  374. int len = mPlayListLen;
  375. for (int i = 0; i < len; i++) {
  376. long n = mPlayList[i];
  377. if (n < 0) {
  378. continue;
  379. } else if (n == 0) {
  380. q.append("0;");
  381. } else {
  382. while (n != 0) {
  383. int digit = (int)(n & 0xf);
  384. n >>>= 4;
  385. q.append(hexdigits[digit]);
  386. }
  387. q.append(";");
  388. }
  389. }
  390. //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
  391. ed.putString("queue", q.toString());
  392. ed.putInt("cardid", mCardId);
  393. if (mShuffleMode != SHUFFLE_NONE) {
  394. // In shuffle mode we need to save the history too
  395. len = mHistory.size();
  396. q.setLength(0);
  397. for (int i = 0; i < len; i++) {
  398. int n = mHistory.get(i);
  399. if (n == 0) {
  400. q.append("0;");
  401. } else {
  402. while (n != 0) {
  403. int digit = (n & 0xf);
  404. n >>>= 4;
  405. q.append(hexdigits[digit]);
  406. }
  407. q.append(";");
  408. }
  409. }
  410. ed.putString("history", q.toString());
  411. }
  412. }
  413. ed.putInt("curpos", mPlayPos);
  414. if (mPlayer.isInitialized()) {
  415. ed.putLong("seekpos", mPlayer.position());
  416. }
  417. ed.putInt("repeatmode", mRepeatMode);
  418. ed.putInt("shufflemode", mShuffleMode);
  419. SharedPreferencesCompat.apply(ed);
  420. //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
  421. }
  422. private void reloadQueue() {
  423. String q = null;
  424. boolean newstyle = false;
  425. int id = mCardId;
  426. if (mPreferences.contains("cardid")) {
  427. newstyle = true;
  428. id = mPreferences.getInt("cardid", ~mCardId);
  429. }
  430. if (id == mCardId) {
  431. // Only restore the saved playlist if the card is still
  432. // the same one as when the playlist was saved
  433. q = mPreferences.getString("queue", "");
  434. }
  435. int qlen = q != null ? q.length() : 0;
  436. if (qlen > 1) {
  437. //Log.i("@@@@ service", "loaded queue: " + q);
  438. int plen = 0;
  439. int n = 0;
  440. int shift = 0;
  441. for (int i = 0; i < qlen; i++) {
  442. char c = q.charAt(i);
  443. if (c == ';') {
  444. ensurePlayListCapacity(plen + 1);
  445. mPlayList[plen] = n;
  446. plen++;
  447. n = 0;
  448. shift = 0;
  449. } else {
  450. if (c >= '0' && c <= '9') {
  451. n += ((c - '0') << shift);
  452. } else if (c >= 'a' && c <= 'f') {
  453. n += ((10 + c - 'a') << shift);
  454. } else {
  455. // bogus playlist data
  456. plen = 0;
  457. break;
  458. }
  459. shift += 4;
  460. }
  461. }
  462. mPlayListLen = plen;
  463. int pos = mPreferences.getInt("curpos", 0);
  464. if (pos < 0 || pos >= mPlayListLen) {
  465. // The saved playlist is bogus, discard it
  466. mPlayListLen = 0;
  467. return;
  468. }
  469. mPlayPos = pos;
  470. // When reloadQueue is called in response to a card-insertion,
  471. // we might not be able to query the media provider right away.
  472. // To deal with this, try querying for the current file, and if
  473. // that fails, wait a while and try again. If that too fails,
  474. // assume there is a problem and don't restore the state.
  475. Cursor crsr = MusicUtils.query(this,
  476. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  477. new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
  478. if (crsr == null || crsr.getCount() == 0) {
  479. // wait a bit and try again
  480. SystemClock.sleep(3000);
  481. crsr = getContentResolver().query(
  482. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  483. mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
  484. }
  485. if (crsr != null) {
  486. crsr.close();
  487. }
  488. // Make sure we don't auto-skip to the next song, since that
  489. // also starts playback. What could happen in that case is:
  490. // - music is paused
  491. // - go to UMS and delete some files, including the currently playing one
  492. // - come back from UMS
  493. // (time passes)
  494. // - music app is killed for some reason (out of memory)
  495. // - music service is restarted, service restores state, doesn't find
  496. // the "current" file, goes to the next and: playback starts on its
  497. // own, potentially at some random inconvenient time.
  498. mOpenFailedCounter = 20;
  499. mQuietMode = true;
  500. openCurrent();
  501. mQuietMode = false;
  502. if (!mPlayer.isInitialized()) {
  503. // couldn't restore the saved state
  504. mPlayListLen = 0;
  505. return;
  506. }
  507. long seekpos = mPreferences.getLong("seekpos", 0);
  508. seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
  509. Log.d(LOGTAG, "restored queue, currently at position "
  510. + position() + "/" + duration()
  511. + " (requested " + seekpos + ")");
  512. int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
  513. if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
  514. repmode = REPEAT_NONE;
  515. }
  516. mRepeatMode = repmode;
  517. int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
  518. if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
  519. shufmode = SHUFFLE_NONE;
  520. }
  521. if (shufmode != SHUFFLE_NONE) {
  522. // in shuffle mode we need to restore the history too
  523. q = mPreferences.getString("history", "");
  524. qlen = q != null ? q.length() : 0;
  525. if (qlen > 1) {
  526. plen = 0;
  527. n = 0;
  528. shift = 0;
  529. mHistory.clear();
  530. for (int i = 0; i < qlen; i++) {
  531. char c = q.charAt(i);
  532. if (c == ';') {
  533. if (n >= mPlayListLen) {
  534. // bogus history data
  535. mHistory.clear();
  536. break;
  537. }
  538. mHistory.add(n);
  539. n = 0;
  540. shift = 0;
  541. } else {
  542. if (c >= '0' && c <= '9') {
  543. n += ((c - '0') << shift);
  544. } else if (c >= 'a' && c <= 'f') {
  545. n += ((10 + c - 'a') << shift);
  546. } else {
  547. // bogus history data
  548. mHistory.clear();
  549. break;
  550. }
  551. shift += 4;
  552. }
  553. }
  554. }
  555. }
  556. if (shufmode == SHUFFLE_AUTO) {
  557. if (! makeAutoShuffleList()) {
  558. shufmode = SHUFFLE_NONE;
  559. }
  560. }
  561. mShuffleMode = shufmode;
  562. }
  563. }
  564. @Override
  565. public IBinder onBind(Intent intent) {
  566. mDelayedStopHandler.removeCallbacksAndMessages(null);
  567. mServiceInUse = true;
  568. return mBinder;
  569. }
  570. @Override
  571. public void onRebind(Intent intent) {
  572. mDelayedStopHandler.removeCallbacksAndMessages(null);
  573. mServiceInUse = true;
  574. }
  575. @Override
  576. public int onStartCommand(Intent intent, int flags, int startId) {
  577. mServiceStartId = startId;
  578. mDelayedStopHandler.removeCallbacksAndMessages(null);
  579. if (intent != null) {
  580. String action = intent.getAction();
  581. String cmd = intent.getStringExtra("command");
  582. MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
  583. if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
  584. next(true);
  585. } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
  586. if (position() < 2000) {
  587. prev();
  588. } else {
  589. seek(0);
  590. play();
  591. }
  592. } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
  593. if (isPlaying()) {
  594. pause();
  595. mPausedByTransientLossOfFocus = false;
  596. } else {
  597. play();
  598. }
  599. } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
  600. pause();
  601. mPausedByTransientLossOfFocus = false;
  602. } else if (CMDPLAY.equals(cmd)) {
  603. play();
  604. } else if (CMDSTOP.equals(cmd)) {
  605. pause();
  606. if (intent.getIntExtra(CMDNOTIF, 0) == 3) {
  607. stopForeground(true);
  608. }
  609. mPausedByTransientLossOfFocus = false;
  610. seek(0);
  611. }
  612. }
  613. // make sure the service will shut down on its own if it was
  614. // just started but not bound to and nothing is playing
  615. mDelayedStopHandler.removeCallbacksAndMessages(null);
  616. Message msg = mDelayedStopHandler.obtainMessage();
  617. mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
  618. return START_STICKY;
  619. }
  620. @Override
  621. public boolean onUnbind(Intent intent) {
  622. mServiceInUse = false;
  623. // Take a snapshot of the current playlist
  624. saveQueue(true);
  625. if (isPlaying() || mPausedByTransientLossOfFocus) {
  626. // something is currently playing, or will be playing once
  627. // an in-progress action requesting audio focus ends, so don't stop the service now.
  628. return true;
  629. }
  630. // If there is a playlist but playback is paused, then wait a while
  631. // before stopping the service, so that pause/resume isn't slow.
  632. // Also delay stopping the service if we're transitioning between tracks.
  633. if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
  634. Message msg = mDelayedStopHandler.obtainMessage();
  635. mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
  636. return true;
  637. }
  638. // No active playlist, OK to stop the service right now
  639. stopSelf(mServiceStartId);
  640. return true;
  641. }
  642. private Handler mDelayedStopHandler = new Handler() {
  643. @Override
  644. public void handleMessage(Message msg) {
  645. // Check again to make sure nothing is playing right now
  646. if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
  647. || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
  648. return;
  649. }
  650. // save the queue again, because it might have changed
  651. // since the user exited the music app (because of
  652. // party-shuffle or because the play-position changed)
  653. saveQueue(true);
  654. stopSelf(mServiceStartId);
  655. }
  656. };
  657. /**
  658. * Called when we receive a ACTION_MEDIA_EJECT notification.
  659. *
  660. * @param storagePath path to mount point for the removed media
  661. */
  662. public void closeExternalStorageFiles(String storagePath) {
  663. // stop playback and clean up if the SD card is going to be unmounted.
  664. stop(true);
  665. notifyChange(QUEUE_CHANGED);
  666. notifyChange(META_CHANGED);
  667. }
  668. /**
  669. * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
  670. * The intent will call closeExternalStorageFiles() if the external media
  671. * is going to be ejected, so applications can clean up any files they have open.
  672. */
  673. public void registerExternalStorageListener() {
  674. if (mUnmountReceiver == null) {
  675. mUnmountReceiver = new BroadcastReceiver() {
  676. @Override
  677. public void onReceive(Context context, Intent intent) {
  678. String action = intent.getAction();
  679. if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
  680. saveQueue(true);
  681. mQueueIsSaveable = false;
  682. closeExternalStorageFiles(intent.getData().getPath());
  683. } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
  684. mMediaMountedCount++;
  685. mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
  686. reloadQueue();
  687. mQueueIsSaveable = true;
  688. notifyChange(QUEUE_CHANGED);
  689. notifyChange(META_CHANGED);
  690. }
  691. }
  692. };
  693. IntentFilter iFilter = new IntentFilter();
  694. iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
  695. iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
  696. iFilter.addDataScheme("file");
  697. registerReceiver(mUnmountReceiver, iFilter);
  698. }
  699. }
  700. /**
  701. * Notify the change-receivers that something has changed.
  702. * The intent that is sent contains the following data
  703. * for the currently playing track:
  704. * "id" - Integer: the database row ID
  705. * "artist" - String: the name of the artist
  706. * "album" - String: the name of the album
  707. * "track" - String: the name of the track
  708. * The intent has an action that is one of
  709. * "com.android.music.metachanged"
  710. * "com.android.music.queuechanged",
  711. * "com.android.music.playbackcomplete"
  712. * "com.android.music.playstatechanged"
  713. * respectively indicating that a new track has
  714. * started playing, that the playback queue has
  715. * changed, that playback has stopped because
  716. * the last file in the list has been played,
  717. * or that the play-state changed (paused/resumed).
  718. */
  719. private void notifyChange(String what) {
  720. Intent i = new Intent(what);
  721. i.putExtra("id", Long.valueOf(getAudioId()));
  722. i.putExtra("artist", getArtistName());
  723. i.putExtra("album",getAlbumName());
  724. i.putExtra("track", getTrackName());
  725. i.putExtra("playing", isPlaying());
  726. sendStickyBroadcast(i);
  727. if (what.equals(PLAYSTATE_CHANGED)) {
  728. mRemoteControlClient.setPlaybackState(isPlaying() ?
  729. RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
  730. } else if (what.equals(META_CHANGED)) {
  731. RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
  732. ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
  733. ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
  734. ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
  735. ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
  736. Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), true);
  737. if (b != null) {
  738. ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
  739. }
  740. ed.apply();
  741. }
  742. if (what.equals(QUEUE_CHANGED)) {
  743. saveQueue(true);
  744. } else {
  745. saveQueue(false);
  746. }
  747. // Share this notification directly with our widgets
  748. mAppWidgetProvider.notifyChange(this, what);
  749. }
  750. private void ensurePlayListCapacity(int size) {
  751. if (mPlayList == null || size > mPlayList.length) {
  752. // reallocate at 2x requested size so we don't
  753. // need to grow and copy the array for every
  754. // insert
  755. long [] newlist = new long[size * 2];
  756. int len = mPlayList != null ? mPlayList.length : mPlayListLen;
  757. for (int i = 0; i < len; i++) {
  758. newlist[i] = mPlayList[i];
  759. }
  760. mPlayList = newlist;
  761. }
  762. // FIXME: shrink the array when the needed size is much smaller
  763. // than the allocated size
  764. }
  765. // insert the list of songs at the specified position in the playlist
  766. private void addToPlayList(long [] list, int position) {
  767. int addlen = list.length;
  768. if (position < 0) { // overwrite
  769. mPlayListLen = 0;
  770. position = 0;
  771. }
  772. ensurePlayListCapacity(mPlayListLen + addlen);
  773. if (position > mPlayListLen) {
  774. position = mPlayListLen;
  775. }
  776. // move part of list after insertion point
  777. int tailsize = mPlayListLen - position;
  778. for (int i = tailsize ; i > 0 ; i--) {
  779. mPlayList[position + i] = mPlayList[position + i - addlen];
  780. }
  781. // copy list into playlist
  782. for (int i = 0; i < addlen; i++) {
  783. mPlayList[position + i] = list[i];
  784. }
  785. mPlayListLen += addlen;
  786. if (mPlayListLen == 0) {
  787. mCursor.close();
  788. mCursor = null;
  789. notifyChange(META_CHANGED);
  790. }
  791. }
  792. /**
  793. * Appends a list of tracks to the current playlist.
  794. * If nothing is playing currently, playback will be started at
  795. * the first track.
  796. * If the action is NOW, playback will switch to the first of
  797. * the new tracks immediately.
  798. * @param list The list of tracks to append.
  799. * @param action NOW, NEXT or LAST
  800. */
  801. public void enqueue(long [] list, int action) {
  802. synchronized(this) {
  803. if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
  804. addToPlayList(list, mPlayPos + 1);
  805. notifyChange(QUEUE_CHANGED);
  806. } else {
  807. // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
  808. addToPlayList(list, Integer.MAX_VALUE);
  809. notifyChange(QUEUE_CHANGED);
  810. if (action == NOW) {
  811. mPlayPos = mPlayListLen - list.length;
  812. openCurrent();
  813. play();
  814. notifyChange(META_CHANGED);
  815. return;
  816. }
  817. }
  818. if (mPlayPos < 0) {
  819. mPlayPos = 0;
  820. openCurrent();
  821. play();
  822. notifyChange(META_CHANGED);
  823. }
  824. }
  825. }
  826. /**
  827. * Replaces the current playlist with a new list,
  828. * and prepares for starting playback at the specified
  829. * position in the list, or a random position if the
  830. * specified position is 0.
  831. * @param list The new list of tracks.
  832. */
  833. public void open(long [] list, int position) {
  834. synchronized (this) {
  835. if (mShuffleMode == SHUFFLE_AUTO) {
  836. mShuffleMode = SHUFFLE_NORMAL;
  837. }
  838. long oldId = getAudioId();
  839. int listlength = list.length;
  840. boolean newlist = true;
  841. if (mPlayListLen == listlength) {
  842. // possible fast path: list might be the same
  843. newlist = false;
  844. for (int i = 0; i < listlength; i++) {
  845. if (list[i] != mPlayList[i]) {
  846. newlist = true;
  847. break;
  848. }
  849. }
  850. }
  851. if (newlist) {
  852. addToPlayList(list, -1);
  853. notifyChange(QUEUE_CHANGED);
  854. }
  855. int oldpos = mPlayPos;
  856. if (position >= 0) {
  857. mPlayPos = position;
  858. } else {
  859. mPlayPos = mRand.nextInt(mPlayListLen);
  860. }
  861. mHistory.clear();
  862. saveBookmarkIfNeeded();
  863. openCurrent();
  864. if (oldId != getAudioId()) {
  865. notifyChange(META_CHANGED);
  866. }
  867. }
  868. }
  869. /**
  870. * Moves the item at index1 to index2.
  871. * @param index1
  872. * @param index2
  873. */
  874. public void moveQueueItem(int index1, int index2) {
  875. synchronized (this) {
  876. if (index1 >= mPlayListLen) {
  877. index1 = mPlayListLen - 1;
  878. }
  879. if (index2 >= mPlayListLen) {
  880. index2 = mPlayListLen - 1;
  881. }
  882. if (index1 < index2) {
  883. long tmp = mPlayList[index1];
  884. for (int i = index1; i < index2; i++) {
  885. mPlayList[i] = mPlayList[i+1];
  886. }
  887. mPlayList[index2] = tmp;
  888. if (mPlayPos == index1) {
  889. mPlayPos = index2;
  890. } else if (mPlayPos >= index1 && mPlayPos <= index2) {
  891. mPlayPos--;
  892. }
  893. } else if (index2 < index1) {
  894. long tmp = mPlayList[index1];
  895. for (int i = index1; i > index2; i--) {
  896. mPlayList[i] = mPlayList[i-1];
  897. }
  898. mPlayList[index2] = tmp;
  899. if (mPlayPos == index1) {
  900. mPlayPos = index2;
  901. } else if (mPlayPos >= index2 && mPlayPos <= index1) {
  902. mPlayPos++;
  903. }
  904. }
  905. notifyChange(QUEUE_CHANGED);
  906. }
  907. }
  908. /**
  909. * Returns the current play list
  910. * @return An array of integers containing the IDs of the tracks in the play list
  911. */
  912. public long [] getQueue() {
  913. synchronized (this) {
  914. int len = mPlayListLen;
  915. long [] list = new long[len];
  916. for (int i = 0; i < len; i++) {
  917. list[i] = mPlayList[i];
  918. }
  919. return list;
  920. }
  921. }
  922. private void openCurrent() {
  923. synchronized (this) {
  924. if (mCursor != null) {
  925. mCursor.close();
  926. mCursor = null;
  927. }
  928. if (mPlayListLen == 0) {
  929. return;
  930. }
  931. stop(false);
  932. String id = String.valueOf(mPlayList[mPlayPos]);
  933. mCursor = getContentResolver().query(
  934. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  935. mCursorCols, "_id=" + id , null, null);
  936. if (mCursor != null) {
  937. mCursor.moveToFirst();
  938. open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
  939. // go to bookmark if needed
  940. if (isPodcast()) {
  941. long bookmark = getBookmark();
  942. // Start playing a little bit before the bookmark,
  943. // so it's easier to get back in to the narrative.
  944. seek(bookmark - 5000);
  945. }
  946. }
  947. }
  948. }
  949. /**
  950. * Opens the specified file and readies it for playback.
  951. *
  952. * @param path The full path of the file to be opened.
  953. */
  954. public void open(String path) {
  955. synchronized (this) {
  956. if (path == null) {
  957. return;
  958. }
  959. // if mCursor is null, try to associate path with a database cursor
  960. if (mCursor == null) {
  961. ContentResolver resolver = getContentResolver();
  962. Uri uri;
  963. String where;
  964. String selectionArgs[];
  965. if (path.startsWith("content://media/")) {
  966. uri = Uri.parse(path);
  967. where = null;
  968. selectionArgs = null;
  969. } else {
  970. uri = MediaStore.Audio.Media.getContentUriForPath(path);
  971. where = MediaStore.Audio.Media.DATA + "=?";
  972. selectionArgs = new String[] { path };
  973. }
  974. try {
  975. mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
  976. if (mCursor != null) {
  977. if (mCursor.getCount() == 0) {
  978. mCursor.close();
  979. mCursor = null;
  980. } else {
  981. mCursor.moveToNext();
  982. ensurePlayListCapacity(1);
  983. mPlayListLen = 1;
  984. mPlayList[0] = mCursor.getLong(IDCOLIDX);
  985. mPlayPos = 0;
  986. }
  987. }
  988. } catch (UnsupportedOperationException ex) {
  989. }
  990. }
  991. mFileToPlay = path;
  992. mPlayer.setDataSource(mFileToPlay);
  993. if (! mPlayer.isInitialized()) {
  994. stop(true);
  995. if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
  996. // beware: this ends up being recursive because next() calls open() again.
  997. next(false);
  998. }
  999. if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
  1000. // need to make sure we only shows this once
  1001. mOpenFailedCounter = 0;
  1002. if (!mQuietMode) {
  1003. Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
  1004. }
  1005. Log.d(LOGTAG, "Failed to open file for playback");
  1006. }
  1007. } else {
  1008. mOpenFailedCounter = 0;
  1009. }
  1010. }
  1011. }
  1012. /**
  1013. * Starts playback of a previously opened file.
  1014. */
  1015. public void play() {
  1016. mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
  1017. AudioManager.AUDIOFOCUS_GAIN);
  1018. mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
  1019. MediaButtonIntentReceiver.class.getName()));
  1020. if (mPlayer.isInitialized()) {
  1021. // if we are at the end of the song, go to the next song first
  1022. long duration = mPlayer.duration();
  1023. if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
  1024. mPlayer.position() >= duration - 2000) {
  1025. next(true);
  1026. }
  1027. mPlayer.start();
  1028. // make sure we fade in, in case a previous fadein was stopped because
  1029. // of another focus loss
  1030. mMediaplayerHandler.removeMessages(FADEDOWN);
  1031. mMediaplayerHandler.sendEmptyMessage(FADEUP);
  1032. RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
  1033. views.setImageViewBitmap(R.id.albumArt, MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), true));
  1034. ComponentName rec = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
  1035. Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
  1036. mediaButtonIntent.putExtra(CMDNOTIF, 1);
  1037. mediaButtonIntent.setComponent(rec);
  1038. KeyEvent mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
  1039. mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, mediaKey);
  1040. PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
  1041. 1, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1042. views.setOnClickPendingIntent(R.id.play, mediaPendingIntent);
  1043. mediaButtonIntent.putExtra(CMDNOTIF, 2);
  1044. mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_MEDIA_NEXT);
  1045. mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, mediaKey);
  1046. mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
  1047. 2, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1048. views.setOnClickPendingIntent(R.id.skip, mediaPendingIntent);
  1049. mediaButtonIntent.putExtra(CMDNOTIF, 3);
  1050. mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_MEDIA_STOP);
  1051. mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, mediaKey);
  1052. mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
  1053. 3, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1054. views.setOnClickPendingIntent(R.id.close, mediaPendingIntent);
  1055. views.setImageViewResource(R.id.play, R.drawable.ic_appwidget_music_pause);
  1056. if (getAudioId() < 0) {
  1057. // streaming
  1058. views.setTextViewText(R.id.trackname, getPath());
  1059. views.setTextViewText(R.id.artistalbum, null);
  1060. } else {
  1061. String artist = getArtistName();
  1062. views.setTextViewText(R.id.trackname, getTrackName());
  1063. if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
  1064. artist = getString(R.string.unknown_artist_name);
  1065. }
  1066. String album = getAlbumName();
  1067. if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
  1068. album = getString(R.string.unknown_album_name);
  1069. }
  1070. views.setTextViewText(R.id.artistalbum,
  1071. getString(R.string.notification_artist_album, artist, album)
  1072. );
  1073. }
  1074. status = new Notification();
  1075. status.contentView = views;
  1076. status.flags |= Notification.FLAG_ONGOING_EVENT;
  1077. status.icon = R.drawable.stat_notify_musicplayer;
  1078. status.contentIntent = PendingIntent.getActivity(this, 0,
  1079. new Intent("com.android.music.PLAYBACK_VIEWER")
  1080. .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
  1081. startForeground(PLAYBACKSERVICE_STATUS, status);
  1082. if (!mIsSupposedToBePlaying) {
  1083. mIsSupposedToBePlaying = true;
  1084. notifyChange(PLAYSTATE_CHANGED);
  1085. }
  1086. } else if (mPlayListLen <= 0) {
  1087. // This is mostly so that if you press 'play' on a bluetooth headset
  1088. // without every having played anything before, it will still play
  1089. // something.
  1090. setShuffleMode(SHUFFLE_AUTO);
  1091. }
  1092. }
  1093. private void stop(boolean remove_status_icon) {
  1094. if (mPlayer.isInitialized()) {
  1095. mPlayer.stop();
  1096. }
  1097. mFileToPlay = null;
  1098. if (mCursor != null) {
  1099. mCursor.close();
  1100. mCursor = null;
  1101. }
  1102. if (remove_status_icon) {
  1103. gotoIdleState();
  1104. } else {
  1105. stopForeground(false);
  1106. }
  1107. if (remove_status_icon) {
  1108. mIsSupposedToBePlaying = false;
  1109. }
  1110. }
  1111. /**
  1112. * Stops playback.
  1113. */
  1114. public void stop() {
  1115. stop(true);
  1116. }
  1117. /**
  1118. * Pauses playback (call play() to resume)
  1119. */
  1120. public void pause() {
  1121. synchronized(this) {
  1122. mMediaplayerHandler.removeMessages(FADEUP);
  1123. if (isPlaying()) {
  1124. mPlayer.pause();
  1125. gotoIdleState();
  1126. mIsSupposedToBePlaying = false;
  1127. notifyChange(PLAYSTATE_CHANGED);
  1128. saveBookmarkIfNeeded();
  1129. }
  1130. }
  1131. }
  1132. /** Returns whether something is currently playing
  1133. *
  1134. * @return true if something is playing (or will be playing shortly, in case
  1135. * we're currently transitioning between tracks), false if not.
  1136. */
  1137. public boolean isPlaying() {
  1138. return mIsSupposedToBePlaying;
  1139. }
  1140. /*
  1141. Desired behavior for prev/next/shuffle:
  1142. - NEXT will move to the next track in the list w…

Large files files are truncated, but you can click here to view the full file