/src/com/andrew/apollo/service/ApolloService.java
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
- /* Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- package com.andrew.apollo.service;
-
- import static com.andrew.apollo.Constants.ALBUM_IMAGE;
- import static com.andrew.apollo.Constants.APOLLO_PREFERENCES;
- import static com.andrew.apollo.Constants.DATA_SCHEME;
-
- import java.io.IOException;
- import java.lang.ref.WeakReference;
- import java.util.Random;
- import java.util.Vector;
-
- import android.app.Notification;
- import android.app.NotificationManager;
- import android.app.PendingIntent;
- import android.app.Service;
- import android.appwidget.AppWidgetManager;
- import android.content.BroadcastReceiver;
- import android.content.ComponentName;
- import android.content.ContentResolver;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.SharedPreferences;
- import android.content.SharedPreferences.Editor;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteException;
- import android.graphics.Bitmap;
- import android.media.AudioManager;
- import android.media.AudioManager.OnAudioFocusChangeListener;
- import android.media.MediaMetadataRetriever;
- import android.media.MediaPlayer;
- import android.media.RemoteControlClient;
- import android.media.RemoteControlClient.MetadataEditor;
- import android.media.audiofx.AudioEffect;
- import android.net.Uri;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.PowerManager;
- import android.os.PowerManager.WakeLock;
- import android.os.SystemClock;
- import android.provider.BaseColumns;
- import android.provider.MediaStore;
- import android.provider.MediaStore.Audio;
- import android.provider.MediaStore.Audio.AudioColumns;
- import android.provider.MediaStore.MediaColumns;
- import android.util.Log;
- import android.view.KeyEvent;
- import android.view.View;
- import android.widget.RemoteViews;
-
- import com.andrew.apollo.IApolloService;
- import com.andrew.apollo.R;
- import com.andrew.apollo.app.widgets.AppWidget42;
- import com.andrew.apollo.utils.ApolloUtils;
- import com.andrew.apollo.utils.MusicUtils;
- import com.andrew.apollo.utils.SharedPreferencesCompat;
- import com.androidquery.AQuery;
-
- public class ApolloService extends Service {
- /**
- * used to specify whether enqueue() should start playing the new list of
- * files right away, next or once all the currently queued files have been
- * played
- */
- public static final int NOW = 1;
-
- public static final int NEXT = 2;
-
- public static final int LAST = 3;
-
- public static final int PLAYBACKSERVICE_STATUS = 1;
-
- public static final int SHUFFLE_NONE = 0;
-
- public static final int SHUFFLE_NORMAL = 1;
-
- public static final int SHUFFLE_AUTO = 2;
-
- public static final int REPEAT_NONE = 0;
-
- public static final int REPEAT_CURRENT = 1;
-
- public static final int REPEAT_ALL = 2;
-
- public static final String APOLLO_PACKAGE_NAME = "com.andrew.apollo";
-
- public static final String MUSIC_PACKAGE_NAME = "com.android.music";
-
- public static final String PLAYSTATE_CHANGED = "com.andrew.apollo.playstatechanged";
-
- public static final String META_CHANGED = "com.andrew.apollo.metachanged";
-
- public static final String QUEUE_CHANGED = "com.andrew.apollo.queuechanged";
-
- public static final String REPEATMODE_CHANGED = "com.andrew.apollo.repeatmodechanged";
-
- public static final String SHUFFLEMODE_CHANGED = "com.andrew.apollo.shufflemodechanged";
-
- public static final String PROGRESSBAR_CHANGED = "com.andrew.apollo.progressbarchnaged";
-
- public static final String REFRESH_PROGRESSBAR = "com.andrew.apollo.refreshprogessbar";
-
- public static final String CYCLEREPEAT_ACTION = "com.andrew.apollo.musicservicecommand.cyclerepeat";
-
- public static final String TOGGLESHUFFLE_ACTION = "com.andrew.apollo.musicservicecommand.toggleshuffle";
-
- public static final String SERVICECMD = "com.andrew.apollo.musicservicecommand";
-
- public static final String CMDNAME = "command";
-
- public static final String CMDTOGGLEPAUSE = "togglepause";
-
- public static final String CMDSTOP = "stop";
-
- public static final String CMDPAUSE = "pause";
-
- public static final String CMDPLAY = "play";
-
- public static final String CMDPREVIOUS = "previous";
-
- public static final String CMDNEXT = "next";
-
- public static final String CMDNOTIF = "buttonId";
-
- public static final String CMDCYCLEREPEAT = "cyclerepeat";
-
- public static final String CMDTOGGLESHUFFLE = "toggleshuffle";
-
- public static final String TOGGLEPAUSE_ACTION = "com.andrew.apollo.musicservicecommand.togglepause";
-
- public static final String PAUSE_ACTION = "com.andrew.apollo.musicservicecommand.pause";
-
- public static final String PREVIOUS_ACTION = "com.andrew.apollo.musicservicecommand.previous";
-
- public static final String NEXT_ACTION = "com.andrew.apollo.musicservicecommand.next";
-
- private static final int TRACK_ENDED = 1;
-
- private static final int RELEASE_WAKELOCK = 2;
-
- private static final int SERVER_DIED = 3;
-
- private static final int FOCUSCHANGE = 4;
-
- private static final int FADEDOWN = 5;
-
- private static final int FADEUP = 6;
-
- private static final int TRACK_WENT_TO_NEXT = 7;
-
- private static final int MAX_HISTORY_SIZE = 100;
-
- private Notification status;
-
- private MultiPlayer mPlayer;
-
- private String mFileToPlay;
-
- private int mShuffleMode = SHUFFLE_NONE;
-
- private int mRepeatMode = REPEAT_NONE;
-
- private int mMediaMountedCount = 0;
-
- private long[] mAutoShuffleList = null;
-
- private long[] mPlayList = null;
-
- private int mPlayListLen = 0;
-
- private final Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
-
- private Cursor mCursor;
-
- private int mPlayPos = -1;
-
- private int mNextPlayPos = -1;
-
- private static final String LOGTAG = "MediaPlaybackService";
-
- private final Shuffler mRand = new Shuffler();
-
- private int mOpenFailedCounter = 0;
-
- String[] mCursorCols = new String[] {
- "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
- MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
- MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
- MediaStore.Audio.Media.ARTIST_ID, MediaStore.Audio.Media.IS_PODCAST,
- MediaStore.Audio.Media.BOOKMARK
- };
-
- private final static int IDCOLIDX = 0;
-
- private final static int PODCASTCOLIDX = 8;
-
- private final static int BOOKMARKCOLIDX = 9;
-
- private BroadcastReceiver mUnmountReceiver = null;
-
- private WakeLock mWakeLock;
-
- private int mServiceStartId = -1;
-
- private boolean mServiceInUse = false;
-
- private boolean mIsSupposedToBePlaying = false;
-
- private boolean mQuietMode = false;
-
- private AudioManager mAudioManager;
-
- private boolean mQueueIsSaveable = true;
-
- // used to track what type of audio focus loss caused the playback to pause
- private boolean mPausedByTransientLossOfFocus = false;
-
- private SharedPreferences mPreferences;
-
- // We use this to distinguish between different cards when saving/restoring
- // playlists.
- // This will have to change if we want to support multiple simultaneous
- // cards.
- private int mCardId;
-
- private final AppWidget42 mAppWidgetProvider4x2 = AppWidget42.getInstance();
-
- // interval after which we stop the service when idle
- private static final int IDLE_DELAY = 60000;
-
- private RemoteControlClient mRemoteControlClient;
-
- private final Handler mMediaplayerHandler = new Handler() {
- float mCurrentVolume = 1.0f;
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case FADEDOWN:
- mCurrentVolume -= .05f;
- if (mCurrentVolume > .2f) {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
- } else {
- mCurrentVolume = .2f;
- }
- mPlayer.setVolume(mCurrentVolume);
- break;
- case FADEUP:
- mCurrentVolume += .01f;
- if (mCurrentVolume < 1.0f) {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
- } else {
- mCurrentVolume = 1.0f;
- }
- mPlayer.setVolume(mCurrentVolume);
- break;
- case SERVER_DIED:
- if (mIsSupposedToBePlaying) {
- gotoNext(true);
- } else {
- // the server died when we were idle, so just
- // reopen the same song (it will start again
- // from the beginning though when the user
- // restarts)
- openCurrentAndNext();
- }
- break;
- case TRACK_WENT_TO_NEXT:
- if (mNextPlayPos >= 0 && mPlayList != null) {
- mPlayPos = mNextPlayPos;
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- mCursor = getCursorForId(mPlayList[mPlayPos]);
- notifyChange(META_CHANGED);
- updateNotification();
- setNextTrack();
- }
- break;
- case TRACK_ENDED:
- if (mRepeatMode == REPEAT_CURRENT) {
- seek(0);
- play();
- } else {
- gotoNext(false);
- }
- break;
- case RELEASE_WAKELOCK:
- mWakeLock.release();
- break;
-
- case FOCUSCHANGE:
- // This code is here so we can better synchronize it with
- // the code that
- // handles fade-in
- switch (msg.arg1) {
- case AudioManager.AUDIOFOCUS_LOSS:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
- if (isPlaying()) {
- mPausedByTransientLossOfFocus = false;
- }
- pause();
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- mMediaplayerHandler.removeMessages(FADEUP);
- mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
- if (isPlaying()) {
- mPausedByTransientLossOfFocus = true;
- }
- pause();
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
- if (!isPlaying() && mPausedByTransientLossOfFocus) {
- mPausedByTransientLossOfFocus = false;
- mCurrentVolume = 0f;
- mPlayer.setVolume(mCurrentVolume);
- play(); // also queues a fade-in
- } else {
- mMediaplayerHandler.removeMessages(FADEDOWN);
- mMediaplayerHandler.sendEmptyMessage(FADEUP);
- }
- break;
- default:
- Log.e(LOGTAG, "Unknown audio focus change code");
- }
- break;
-
- default:
- break;
- }
- }
- };
-
- private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String cmd = intent.getStringExtra("command");
- if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
- gotoNext(true);
- } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
- prev();
- } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
- if (isPlaying()) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else {
- play();
- }
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else if (CMDPLAY.equals(cmd)) {
- play();
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- seek(0);
- } else if (CMDCYCLEREPEAT.equals(cmd) || CYCLEREPEAT_ACTION.equals(action)) {
- cycleRepeat();
- } else if (CMDTOGGLESHUFFLE.equals(cmd) || TOGGLESHUFFLE_ACTION.equals(action)) {
- toggleShuffle();
- } else if (AppWidget42.CMDAPPWIDGETUPDATE.equals(cmd)) {
- int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- mAppWidgetProvider4x2.performUpdate(ApolloService.this, appWidgetIds);
- }
- }
- };
-
- private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
- @Override
- public void onAudioFocusChange(int focusChange) {
- mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
- }
- };
-
- public ApolloService() {
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
- ComponentName rec = new ComponentName(getPackageName(),
- MediaButtonIntentReceiver.class.getName());
- mAudioManager.registerMediaButtonEventReceiver(rec);
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.setComponent(rec);
- PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0,
- mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
- mAudioManager.registerRemoteControlClient(mRemoteControlClient);
-
- int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
- | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
- | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
- | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
- | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
- mRemoteControlClient.setTransportControlFlags(flags);
-
- mPreferences = getSharedPreferences(APOLLO_PREFERENCES, MODE_WORLD_READABLE
- | MODE_WORLD_WRITEABLE);
- mCardId = MusicUtils.getCardId(this);
-
- registerExternalStorageListener();
-
- // Needs to be done in this thread, since otherwise
- // ApplicationContext.getPowerManager() crashes.
- mPlayer = new MultiPlayer();
- mPlayer.setHandler(mMediaplayerHandler);
-
- reloadQueue();
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
-
- IntentFilter commandFilter = new IntentFilter();
- commandFilter.addAction(SERVICECMD);
- commandFilter.addAction(TOGGLEPAUSE_ACTION);
- commandFilter.addAction(PAUSE_ACTION);
- commandFilter.addAction(NEXT_ACTION);
- commandFilter.addAction(PREVIOUS_ACTION);
- commandFilter.addAction(CYCLEREPEAT_ACTION);
- commandFilter.addAction(TOGGLESHUFFLE_ACTION);
- registerReceiver(mIntentReceiver, commandFilter);
-
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
- mWakeLock.setReferenceCounted(false);
-
- // If the service was idle, but got killed before it stopped itself, the
- // system will relaunch it. Make sure it gets stopped again in that
- // case.
- Message msg = mDelayedStopHandler.obtainMessage();
- mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
- }
-
- @Override
- public void onDestroy() {
- // Check that we're not being destroyed while something is still
- // playing.
- if (mIsSupposedToBePlaying) {
- Log.e(LOGTAG, "Service being destroyed while still playing.");
- }
- // release all MediaPlayer resources, including the native player and
- // wakelocks
- Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
- i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
- i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
- sendBroadcast(i);
- mPlayer.release();
- mPlayer = null;
-
- mAudioManager.abandonAudioFocus(mAudioFocusListener);
- mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
-
- // make sure there aren't any other messages coming
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mMediaplayerHandler.removeCallbacksAndMessages(null);
-
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
-
- unregisterReceiver(mIntentReceiver);
- if (mUnmountReceiver != null) {
- unregisterReceiver(mUnmountReceiver);
- mUnmountReceiver = null;
- }
- mWakeLock.release();
- super.onDestroy();
- }
-
- private final char hexdigits[] = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
- };
-
- private void saveQueue(boolean full) {
- if (!mQueueIsSaveable) {
- return;
- }
-
- Editor ed = mPreferences.edit();
- // long start = System.currentTimeMillis();
- if (full) {
- StringBuilder q = new StringBuilder();
-
- // The current playlist is saved as a list of "reverse hexadecimal"
- // numbers, which we can generate faster than normal decimal or
- // hexadecimal numbers, which in turn allows us to save the playlist
- // more often without worrying too much about performance.
- // (saving the full state takes about 40 ms under no-load conditions
- // on the phone)
- int len = mPlayListLen;
- for (int i = 0; i < len; i++) {
- long n = mPlayList[i];
- if (n < 0) {
- continue;
- } else if (n == 0) {
- q.append("0;");
- } else {
- while (n != 0) {
- int digit = (int)(n & 0xf);
- n >>>= 4;
- q.append(hexdigits[digit]);
- }
- q.append(";");
- }
- }
- // Log.i("@@@@ service", "created queue string in " +
- // (System.currentTimeMillis() - start) + " ms");
- ed.putString("queue", q.toString());
- ed.putInt("cardid", mCardId);
- if (mShuffleMode != SHUFFLE_NONE) {
- // In shuffle mode we need to save the history too
- len = mHistory.size();
- q.setLength(0);
- for (int i = 0; i < len; i++) {
- int n = mHistory.get(i);
- if (n == 0) {
- q.append("0;");
- } else {
- while (n != 0) {
- int digit = (n & 0xf);
- n >>>= 4;
- q.append(hexdigits[digit]);
- }
- q.append(";");
- }
- }
- ed.putString("history", q.toString());
- }
- }
- ed.putInt("curpos", mPlayPos);
- if (mPlayer.isInitialized()) {
- ed.putLong("seekpos", mPlayer.position());
- }
- ed.putInt("repeatmode", mRepeatMode);
- ed.putInt("shufflemode", mShuffleMode);
- SharedPreferencesCompat.apply(ed);
-
- // Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis()
- // - start) + " ms");
- }
-
- private void reloadQueue() {
- String q = null;
-
- int id = mCardId;
- if (mPreferences.contains("cardid")) {
- id = mPreferences.getInt("cardid", ~mCardId);
- }
- if (id == mCardId) {
- // Only restore the saved playlist if the card is still
- // the same one as when the playlist was saved
- q = mPreferences.getString("queue", "");
- }
- int qlen = q != null ? q.length() : 0;
- if (qlen > 1) {
- // Log.i("@@@@ service", "loaded queue: " + q);
- int plen = 0;
- int n = 0;
- int shift = 0;
- for (int i = 0; i < qlen; i++) {
- char c = q.charAt(i);
- if (c == ';') {
- ensurePlayListCapacity(plen + 1);
- mPlayList[plen] = n;
- plen++;
- n = 0;
- shift = 0;
- } else {
- if (c >= '0' && c <= '9') {
- n += ((c - '0') << shift);
- } else if (c >= 'a' && c <= 'f') {
- n += ((10 + c - 'a') << shift);
- } else {
- // bogus playlist data
- plen = 0;
- break;
- }
- shift += 4;
- }
- }
- mPlayListLen = plen;
-
- int pos = mPreferences.getInt("curpos", 0);
- if (pos < 0 || pos >= mPlayListLen) {
- // The saved playlist is bogus, discard it
- mPlayListLen = 0;
- return;
- }
- mPlayPos = pos;
-
- // When reloadQueue is called in response to a card-insertion,
- // we might not be able to query the media provider right away.
- // To deal with this, try querying for the current file, and if
- // that fails, wait a while and try again. If that too fails,
- // assume there is a problem and don't restore the state.
- Cursor crsr = MusicUtils.query(this, Audio.Media.EXTERNAL_CONTENT_URI, new String[] {
- "_id"
- }, "_id=" + mPlayList[mPlayPos], null, null);
- if (crsr == null || crsr.getCount() == 0) {
- // wait a bit and try again
- SystemClock.sleep(3000);
- crsr = getContentResolver().query(Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
- "_id=" + mPlayList[mPlayPos], null, null);
- }
- if (crsr != null) {
- crsr.close();
- }
-
- // Make sure we don't auto-skip to the next song, since that
- // also starts playback. What could happen in that case is:
- // - music is paused
- // - go to UMS and delete some files, including the currently
- // playing one
- // - come back from UMS
- // (time passes)
- // - music app is killed for some reason (out of memory)
- // - music service is restarted, service restores state, doesn't
- // find
- // the "current" file, goes to the next and: playback starts on its
- // own, potentially at some random inconvenient time.
- mOpenFailedCounter = 20;
- mQuietMode = true;
- openCurrentAndNext();
- mQuietMode = false;
- if (!mPlayer.isInitialized()) {
- // couldn't restore the saved state
- mPlayListLen = 0;
- return;
- }
-
- long seekpos = mPreferences.getLong("seekpos", 0);
- seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
- Log.d(LOGTAG, "restored queue, currently at position " + position() + "/" + duration()
- + " (requested " + seekpos + ")");
-
- int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
- if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
- repmode = REPEAT_NONE;
- }
- mRepeatMode = repmode;
-
- int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
- if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
- shufmode = SHUFFLE_NONE;
- }
- if (shufmode != SHUFFLE_NONE) {
- // in shuffle mode we need to restore the history too
- q = mPreferences.getString("history", "");
- qlen = q != null ? q.length() : 0;
- if (qlen > 1) {
- plen = 0;
- n = 0;
- shift = 0;
- mHistory.clear();
- for (int i = 0; i < qlen; i++) {
- char c = q.charAt(i);
- if (c == ';') {
- if (n >= mPlayListLen) {
- // bogus history data
- mHistory.clear();
- break;
- }
- mHistory.add(n);
- n = 0;
- shift = 0;
- } else {
- if (c >= '0' && c <= '9') {
- n += ((c - '0') << shift);
- } else if (c >= 'a' && c <= 'f') {
- n += ((10 + c - 'a') << shift);
- } else {
- // bogus history data
- mHistory.clear();
- break;
- }
- shift += 4;
- }
- }
- }
- }
- if (shufmode == SHUFFLE_AUTO) {
- if (!makeAutoShuffleList()) {
- shufmode = SHUFFLE_NONE;
- }
- }
- mShuffleMode = shufmode;
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mServiceInUse = true;
- return mBinder;
- }
-
- @Override
- public void onRebind(Intent intent) {
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mServiceInUse = true;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- mServiceStartId = startId;
- mDelayedStopHandler.removeCallbacksAndMessages(null);
-
- if (intent != null) {
- String action = intent.getAction();
- String cmd = intent.getStringExtra("command");
-
- if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
- gotoNext(true);
- } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
- if (position() < 2000) {
- prev();
- } else {
- seek(0);
- play();
- }
- } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
- if (mIsSupposedToBePlaying) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else {
- play();
- }
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else if (CMDPLAY.equals(cmd)) {
- play();
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- if (intent.getIntExtra(CMDNOTIF, 0) == 3) {
- stopForeground(true);
- }
- mPausedByTransientLossOfFocus = false;
- seek(0);
- } else if (CMDCYCLEREPEAT.equals(cmd) || CYCLEREPEAT_ACTION.equals(action)) {
- cycleRepeat();
- } else if (CMDTOGGLESHUFFLE.equals(cmd) || TOGGLESHUFFLE_ACTION.equals(action)) {
- toggleShuffle();
- }
- }
-
- // make sure the service will shut down on its own if it was
- // just started but not bound to and nothing is playing
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- Message msg = mDelayedStopHandler.obtainMessage();
- mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
- return START_STICKY;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- mServiceInUse = false;
-
- // Take a snapshot of the current playlist
- saveQueue(true);
-
- if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {
- // something is currently playing, or will be playing once
- // an in-progress action requesting audio focus ends, so don't stop
- // the service now.
- return true;
- }
-
- // If there is a playlist but playback is paused, then wait a while
- // before stopping the service, so that pause/resume isn't slow.
- // Also delay stopping the service if we're transitioning between
- // tracks.
- if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
- Message msg = mDelayedStopHandler.obtainMessage();
- mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
- return true;
- }
-
- // No active playlist, OK to stop the service right now
- stopSelf(mServiceStartId);
- return true;
- }
-
- private final Handler mDelayedStopHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // Check again to make sure nothing is playing right now
- if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
- || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
- return;
- }
- // save the queue again, because it might have changed
- // since the user exited the music app (because of
- // party-shuffle or because the play-position changed)
- saveQueue(true);
- stopSelf(mServiceStartId);
- }
- };
-
- /**
- * Called when we receive a ACTION_MEDIA_EJECT notification.
- *
- * @param storagePath path to mount point for the removed media
- */
- public void closeExternalStorageFiles(String storagePath) {
- // stop playback and clean up if the SD card is going to be unmounted.
- stop(true);
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
- }
-
- /**
- * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The
- * intent will call closeExternalStorageFiles() if the external media is
- * going to be ejected, so applications can clean up any files they have
- * open.
- */
- public void registerExternalStorageListener() {
- if (mUnmountReceiver == null) {
- mUnmountReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
- saveQueue(true);
- mQueueIsSaveable = false;
- closeExternalStorageFiles(intent.getData().getPath());
- } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
- mMediaMountedCount++;
- mCardId = MusicUtils.getCardId(ApolloService.this);
- reloadQueue();
- mQueueIsSaveable = true;
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
- }
- }
- };
- IntentFilter iFilter = new IntentFilter();
- iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
- iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- iFilter.addDataScheme(DATA_SCHEME);
- registerReceiver(mUnmountReceiver, iFilter);
- }
- }
-
- /**
- * Notify the change-receivers that something has changed. The intent that
- * is sent contains the following data for the currently playing track: "id"
- * - Integer: the database row ID "artist" - String: the name of the artist
- * "album" - String: the name of the album "track" - String: the name of the
- * track The intent has an action that is one of
- * "com.andrew.apollo.metachanged" "com.andrew.apollo.queuechanged",
- * "com.andrew.apollo.playbackcomplete" "com.andrew.apollo.playstatechanged"
- * respectively indicating that a new track has started playing, that the
- * playback queue has changed, that playback has stopped because the last
- * file in the list has been played, or that the play-state changed
- * (paused/resumed).
- */
- private void notifyChange(String what) {
-
- Intent i = new Intent(what);
- i.putExtra("id", Long.valueOf(getAudioId()));
- i.putExtra("artist", getArtistName());
- i.putExtra("album", getAlbumName());
- i.putExtra("track", getTrackName());
- i.putExtra("playing", mIsSupposedToBePlaying);
- sendStickyBroadcast(i);
-
- i = new Intent(i);
- i.setAction(what.replace(APOLLO_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
- sendStickyBroadcast(i);
-
- if (what.equals(PLAYSTATE_CHANGED)) {
- mRemoteControlClient
- .setPlaybackState(mIsSupposedToBePlaying ? RemoteControlClient.PLAYSTATE_PLAYING
- : RemoteControlClient.PLAYSTATE_PAUSED);
- } else if (what.equals(META_CHANGED)) {
- RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
- ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
- ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
- ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
- ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
- AQuery aq = new AQuery(this);
- Bitmap b = aq
- .getCachedImage(ApolloUtils.getImageURL(getAlbumName(), ALBUM_IMAGE, this));
- if (b != null) {
- ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
- }
- ed.apply();
- }
-
- if (what.equals(QUEUE_CHANGED)) {
- saveQueue(true);
- } else {
- saveQueue(false);
- }
-
- mAppWidgetProvider4x2.notifyChange(this, what);
-
- }
-
- private void ensurePlayListCapacity(int size) {
- if (mPlayList == null || size > mPlayList.length) {
- // reallocate at 2x requested size so we don't
- // need to grow and copy the array for every
- // insert
- long[] newlist = new long[size * 2];
- int len = mPlayList != null ? mPlayList.length : mPlayListLen;
- for (int i = 0; i < len; i++) {
- newlist[i] = mPlayList[i];
- }
- mPlayList = newlist;
- }
- // FIXME: shrink the array when the needed size is much smaller
- // than the allocated size
- }
-
- // insert the list of songs at the specified position in the playlist
- private void addToPlayList(long[] list, int position) {
- int addlen = list.length;
- if (position < 0) { // overwrite
- mPlayListLen = 0;
- position = 0;
- }
- ensurePlayListCapacity(mPlayListLen + addlen);
- if (position > mPlayListLen) {
- position = mPlayListLen;
- }
-
- // move part of list after insertion point
- int tailsize = mPlayListLen - position;
- for (int i = tailsize; i > 0; i--) {
- mPlayList[position + i] = mPlayList[position + i - addlen];
- }
-
- // copy list into playlist
- for (int i = 0; i < addlen; i++) {
- mPlayList[position + i] = list[i];
- }
- mPlayListLen += addlen;
- if (mPlayListLen == 0) {
- mCursor.close();
- mCursor = null;
- notifyChange(META_CHANGED);
- }
- }
-
- /**
- * Appends a list of tracks to the current playlist. If nothing is playing
- * currently, playback will be started at the first track. If the action is
- * NOW, playback will switch to the first of the new tracks immediately.
- *
- * @param list The list of tracks to append.
- * @param action NOW, NEXT or LAST
- */
- public void enqueue(long[] list, int action) {
- synchronized (this) {
- if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
- addToPlayList(list, mPlayPos + 1);
- notifyChange(QUEUE_CHANGED);
- } else {
- // action == LAST || action == NOW || mPlayPos + 1 ==
- // mPlayListLen
- addToPlayList(list, Integer.MAX_VALUE);
- notifyChange(QUEUE_CHANGED);
- if (action == NOW) {
- mPlayPos = mPlayListLen - list.length;
- openCurrentAndNext();
- play();
- notifyChange(META_CHANGED);
- return;
- }
- }
- if (mPlayPos < 0) {
- mPlayPos = 0;
- openCurrentAndNext();
- play();
- notifyChange(META_CHANGED);
- }
- }
- }
-
- /**
- * Replaces the current playlist with a new list, and prepares for starting
- * playback at the specified position in the list, or a random position if
- * the specified position is 0.
- *
- * @param list The new list of tracks.
- */
- public void open(long[] list, int position) {
- synchronized (this) {
- if (mShuffleMode == SHUFFLE_AUTO) {
- mShuffleMode = SHUFFLE_NORMAL;
- }
- long oldId = getAudioId();
- int listlength = list.length;
- boolean newlist = true;
- if (mPlayListLen == listlength) {
- // possible fast path: list might be the same
- newlist = false;
- for (int i = 0; i < listlength; i++) {
- if (list[i] != mPlayList[i]) {
- newlist = true;
- break;
- }
- }
- }
- if (newlist) {
- addToPlayList(list, -1);
- notifyChange(QUEUE_CHANGED);
- }
- if (position >= 0) {
- mPlayPos = position;
- } else {
- mPlayPos = mRand.nextInt(mPlayListLen);
- }
- mHistory.clear();
-
- saveBookmarkIfNeeded();
- openCurrentAndNext();
- if (oldId != getAudioId()) {
- notifyChange(META_CHANGED);
- }
- }
- }
-
- /**
- * Returns the current play list
- *
- * @return An array of integers containing the IDs of the tracks in the play
- * list
- */
- public long[] getQueue() {
- synchronized (this) {
- int len = mPlayListLen;
- long[] list = new long[len];
- for (int i = 0; i < len; i++) {
- list[i] = mPlayList[i];
- }
- return list;
- }
- }
-
- private Cursor getCursorForId(long lid) {
- String id = String.valueOf(lid);
-
- Cursor c = getContentResolver().query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- mCursorCols, "_id=" + id , null, null);
- if (c != null) {
- c.moveToFirst();
- }
- return c;
- }
-
- private void openCurrentAndNext() {
- synchronized (this) {
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
-
- if (mPlayListLen == 0) {
- return;
- }
- stop(false);
- mCursor = getCursorForId(mPlayList[mPlayPos]);
- if (mCursor == null ) {
- return;
- }
- while(!open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + mCursor.getLong(IDCOLIDX))) {
- if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
- int pos = getNextPosition(false);
- if (pos < 0) {
- gotoIdleState();
- if (mIsSupposedToBePlaying) {
- mIsSupposedToBePlaying = false;
- notifyChange(PLAYSTATE_CHANGED);
- }
- return;
- }
- mPlayPos = pos;
- stop(false);
- mPlayPos = pos;
- mCursor = getCursorForId(mPlayList[mPlayPos]);
- } else {
- mOpenFailedCounter = 0;
- Log.d(LOGTAG, "Failed to open file for playback");
- return;
- }
- }
-
- // go to bookmark if needed
- if (isPodcast()) {
- long bookmark = getBookmark();
- // Start playing a little bit before the bookmark,
- // so it's easier to get back in to the narrative.
- seek(bookmark - 5000);
- }
- setNextTrack();
-
- }
- }
-
- private void setNextTrack() {
- mNextPlayPos = getNextPosition(false);
- if (mNextPlayPos >= 0 && mPlayList != null) {
- long id = mPlayList[mNextPlayPos];
- mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
- }
- }
-
- /**
- * Opens the specified file and readies it for playback.
- *
- * @param path The full path of the file to be opened.
- */
- public boolean open(String path) {
- synchronized (this) {
- if (path == null) {
- return false;
- }
-
- // if mCursor is null, try to associate path with a database cursor
- if (mCursor == null) {
-
- ContentResolver resolver = getContentResolver();
- Uri uri;
- String where;
- String selectionArgs[];
- if (path.startsWith("content://media/")) {
- uri = Uri.parse(path);
- where = null;
- selectionArgs = null;
- } else {
- // Remove schema for search in the database
- // Otherwise the file will not found
- String data = path;
- if( data.startsWith("file://") ){
- data = data.substring(7);
- }
- uri = MediaStore.Audio.Media.getContentUriForPath(path);
- where = MediaColumns.DATA + "=?";
- selectionArgs = new String[] {
- data
- };
- }
-
- try {
- mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
- if (mCursor != null) {
- if (mCursor.getCount() == 0) {
- mCursor.close();
- mCursor = null;
- } else {
- mCursor.moveToNext();
- ensurePlayListCapacity(1);
- mPlayListLen = 1;
- mPlayList[0] = mCursor.getLong(IDCOLIDX);
- mPlayPos = 0;
- }
- }
- } catch (UnsupportedOperationException ex) {
- }
- }
- mFileToPlay = path;
- mPlayer.setDataSource(mFileToPlay);
- if (mPlayer.isInitialized()) {
- mOpenFailedCounter = 0;
- return true;
- }
- stop(true);
- return false;
- }
- }
-
- /**
- * Method that query the media database for search a path an translate
- * to the internal media id
- *
- * @param path The path to search
- * @return long The id of the resource, or -1 if not found
- */
- public long getIdFromPath(String path) {
- try {
- // Remove schema for search in the database
- // Otherwise the file will not found
- String data = path;
- if( data.startsWith("file://") ){
- data = data.substring(7);
- }
- ContentResolver resolver = getContentResolver();
- String where = MediaColumns.DATA + "=?";
- String selectionArgs[] = new String[] {
- data
- };
- Cursor cursor =
- resolver.query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- mCursorCols, where, selectionArgs, null);
- try {
- if (cursor == null || cursor.getCount() == 0) {
- return -1;
- }
- cursor.moveToNext();
- return cursor.getLong(IDCOLIDX);
- } finally {
- try {
- if( cursor != null )
- cursor.close();
- } catch (Exception ex) {
- }
- }
- } catch (UnsupportedOperationException ex) {
- }
- return -1;
- }
-
- /**
- * Starts playback of a previously opened file.
- */
- public void play() {
- mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
- mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
- MediaButtonIntentReceiver.class.getName()));
-
- if (mPlayer.isInitialized()) {
- // if we are at the end of the song, go to the next song first
-
- mPlayer.start();
- // make sure we fade in, in case a previous fadein was stopped
- // because
- // of another focus loss
- mMediaplayerHandler.removeMessages(FADEDOWN);
- mMediaplayerHandler.sendEmptyMessage(FADEUP);
-
- updateNotification();
- if (!mIsSupposedToBePlaying) {
- mIsSupposedToBePlaying = true;
- notifyChange(PLAYSTATE_CHANGED);
- }
- } else if (mPlayListLen <= 0) {
- // This is mostly so that if you press 'play' on a bluetooth headset
- // without every having played anything before, it will still play
- // something.
- setShuffleMode(SHUFFLE_AUTO);
- }
- }
-
- private void updateNotification() {
- AQuery aq = new AQuery(this);
- Bitmap b = aq.getCachedImage(ApolloUtils.getImageURL(getAlbumName(), ALBUM_IMAGE, this));
-
- RemoteViews views = new RemoteViews(getPackageName(), R.layout.status_bar);
- if (b != null) {
- views.setViewVisibility(R.id.status_bar_icon, View.GONE);
- views.setViewVisibility(R.id.status_bar…
Large files files are truncated, but you can click here to view the full file