PageRenderTime 148ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Examples/AppFundamentalsVideoSourceExamples/src/MediaPlaybackService.java

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