PageRenderTime 174ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/andrew/apollo/service/ApolloService.java

https://bitbucket.org/tbeemster/rhythm
Java | 2356 lines | 1749 code | 280 blank | 327 comment | 410 complexity | 65a92e81eede5e9d1a13180e97994635 MD5 | raw file

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

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

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