PageRenderTime 100ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

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

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

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