blob: f0a99d710e500da20314e271e929b6d2b9330b3b [file] [log] [blame]
The Android Open Source Project792a2202009-03-03 19:32:30 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.music;
18
19import android.app.Notification;
The Android Open Source Project792a2202009-03-03 19:32:30 -080020import android.app.PendingIntent;
21import android.app.Service;
The Android Open Source Project490384b2009-03-11 12:11:59 -070022import android.appwidget.AppWidgetManager;
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -070023import android.content.ComponentName;
The Android Open Source Project792a2202009-03-03 19:32:30 -080024import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.BroadcastReceiver;
31import android.content.SharedPreferences;
32import android.content.SharedPreferences.Editor;
33import android.database.Cursor;
34import android.database.sqlite.SQLiteException;
Marco Nelissen8717d342011-09-08 09:32:51 -070035import android.graphics.Bitmap;
Marco Nelissenf2ef3b52010-09-21 15:47:27 -070036import android.media.audiofx.AudioEffect;
The Android Open Source Project792a2202009-03-03 19:32:30 -080037import android.media.AudioManager;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -080038import android.media.AudioManager.OnAudioFocusChangeListener;
Marco Nelissen8717d342011-09-08 09:32:51 -070039import android.media.MediaMetadataRetriever;
The Android Open Source Project792a2202009-03-03 19:32:30 -080040import android.media.MediaPlayer;
Marco Nelissen30867022012-03-14 16:04:34 -070041import android.media.MediaPlayer.OnCompletionListener;
Marco Nelissen8717d342011-09-08 09:32:51 -070042import android.media.RemoteControlClient;
43import android.media.RemoteControlClient.MetadataEditor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080044import android.net.Uri;
The Android Open Source Project792a2202009-03-03 19:32:30 -080045import android.os.Handler;
46import android.os.IBinder;
47import android.os.Message;
48import android.os.PowerManager;
49import android.os.SystemClock;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +053050import android.os.SystemProperties;
The Android Open Source Project792a2202009-03-03 19:32:30 -080051import android.os.PowerManager.WakeLock;
52import android.provider.MediaStore;
53import android.util.Log;
54import android.widget.RemoteViews;
55import android.widget.Toast;
yanmei.zhang81deb3d2015-06-08 19:54:48 +080056import android.telephony.TelephonyManager;
Marco Nelissen39888902010-03-02 10:27:05 -080057import java.io.FileDescriptor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080058import java.io.IOException;
Marco Nelissen39888902010-03-02 10:27:05 -080059import java.io.PrintWriter;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070060import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080061import java.util.Random;
62import java.util.Vector;
rakesh reddyf032abb2014-09-24 15:47:45 +053063import java.util.HashMap;
The Android Open Source Project792a2202009-03-03 19:32:30 -080064
65/**
66 * Provides "background" audio playback capabilities, allowing the
67 * user to switch between activities without stopping playback.
68 */
69public class MediaPlaybackService extends Service {
70 /** used to specify whether enqueue() should start playing
71 * the new list of files right away, next or once all the currently
72 * queued files have been played
73 */
74 public static final int NOW = 1;
75 public static final int NEXT = 2;
76 public static final int LAST = 3;
77 public static final int PLAYBACKSERVICE_STATUS = 1;
78
79 public static final int SHUFFLE_NONE = 0;
80 public static final int SHUFFLE_NORMAL = 1;
81 public static final int SHUFFLE_AUTO = 2;
82
83 public static final int REPEAT_NONE = 0;
84 public static final int REPEAT_CURRENT = 1;
85 public static final int REPEAT_ALL = 2;
rakesh reddyf032abb2014-09-24 15:47:45 +053086 private HashMap<Byte, Boolean> mAttributePairs = new HashMap<Byte, Boolean>();
The Android Open Source Project792a2202009-03-03 19:32:30 -080087
88 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
89 public static final String META_CHANGED = "com.android.music.metachanged";
Ayan Ghosh6ceaa692014-07-23 21:03:40 +053090 public static final String SHUFFLE_CHANGED = "com.android.music.shuffle";
91 public static final String REPEAT_CHANGED = "com.android.music.repeat";
The Android Open Source Project792a2202009-03-03 19:32:30 -080092 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
The Android Open Source Project792a2202009-03-03 19:32:30 -080093
94 public static final String SERVICECMD = "com.android.music.musicservicecommand";
95 public static final String CMDNAME = "command";
96 public static final String CMDTOGGLEPAUSE = "togglepause";
97 public static final String CMDSTOP = "stop";
98 public static final String CMDPAUSE = "pause";
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -070099 public static final String CMDPLAY = "play";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800100 public static final String CMDPREVIOUS = "previous";
101 public static final String CMDNEXT = "next";
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530102 public static final String CMDGET = "get";
103 public static final String CMDSET = "set";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104
105 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
106 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
107 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
108 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530109 private static final String PLAYSTATUS_REQUEST = "org.codeaurora.android.music.playstatusrequest";
110 private static final String PLAYSTATUS_RESPONSE = "org.codeaurora.music.playstatusresponse";
111 private static final String PLAYERSETTINGS_REQUEST = "org.codeaurora.music.playersettingsrequest";
112 private static final String PLAYERSETTINGS_RESPONSE = "org.codeaurora.music.playersettingsresponse";
113 private static final String SET_ADDRESSED_PLAYER = "org.codeaurora.music.setaddressedplayer";
114 private static final String EXTRA_SHUFFLE_VAL = "shuffle";
115 private static final String EXTRA_REPEAT_VAL = "repeat";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800116
The Android Open Source Project792a2202009-03-03 19:32:30 -0800117 private static final int TRACK_ENDED = 1;
118 private static final int RELEASE_WAKELOCK = 2;
119 private static final int SERVER_DIED = 3;
Marco Nelissen7181da82010-12-01 16:39:04 -0800120 private static final int FOCUSCHANGE = 4;
Marco Nelissen2da473d2010-09-29 16:11:12 -0700121 private static final int FADEDOWN = 5;
122 private static final int FADEUP = 6;
Marco Nelissene41bd182012-03-14 08:24:40 -0700123 private static final int TRACK_WENT_TO_NEXT = 7;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530124 private static final int ERROR = 8 ;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700125 private static final int MAX_HISTORY_SIZE = 100;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530126 private static final int DEFAULT_REPEAT_VAL = 0;
127 private static final int DEFAULT_SHUFFLE_VAL = 0;
128 private static final int SET_BROWSED_PLAYER = 1001;
129 private static final int SET_PLAY_ITEM = 1002;
130 private static final int GET_NOW_PLAYING_ENTRIES = 1003;
131
132 private static final int SCOPE_FILE_SYSTEM = 0x01;
133 private static final int SCOPE_NOW_PLAYING = 0x03;
134 private static final int INVALID_SONG_UID = 0xffffffff;
135
The Android Open Source Project792a2202009-03-03 19:32:30 -0800136 private MultiPlayer mPlayer;
137 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800138 private int mShuffleMode = SHUFFLE_NONE;
139 private int mRepeatMode = REPEAT_NONE;
140 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700141 private long [] mAutoShuffleList = null;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700142 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800143 private int mPlayListLen = 0;
144 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
145 private Cursor mCursor;
146 private int mPlayPos = -1;
Marco Nelissene41bd182012-03-14 08:24:40 -0700147 private int mNextPlayPos = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800148 private static final String LOGTAG = "MediaPlaybackService";
149 private final Shuffler mRand = new Shuffler();
150 private int mOpenFailedCounter = 0;
151 String[] mCursorCols = new String[] {
152 "audio._id AS _id", // index must match IDCOLIDX below
153 MediaStore.Audio.Media.ARTIST,
154 MediaStore.Audio.Media.ALBUM,
155 MediaStore.Audio.Media.TITLE,
156 MediaStore.Audio.Media.DATA,
157 MediaStore.Audio.Media.MIME_TYPE,
158 MediaStore.Audio.Media.ALBUM_ID,
159 MediaStore.Audio.Media.ARTIST_ID,
160 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
161 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
162 };
163 private final static int IDCOLIDX = 0;
164 private final static int PODCASTCOLIDX = 8;
165 private final static int BOOKMARKCOLIDX = 9;
166 private BroadcastReceiver mUnmountReceiver = null;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530167 private BroadcastReceiver mA2dpReceiver = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800168 private WakeLock mWakeLock;
169 private int mServiceStartId = -1;
170 private boolean mServiceInUse = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700171 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800172 private boolean mQuietMode = false;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800173 private AudioManager mAudioManager;
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700174 private boolean mQueueIsSaveable = true;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800175 // used to track what type of audio focus loss caused the playback to pause
176 private boolean mPausedByTransientLossOfFocus = false;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530177 private SetBrowsedPlayerMonitor mSetBrowsedPlayerMonitor;
178 private SetPlayItemMonitor mSetPlayItemMonitor;
179 private GetNowPlayingEntriesMonitor mGetNowPlayingEntriesMonitor;
rakesh reddyf032abb2014-09-24 15:47:45 +0530180 public static final byte ATTRIBUTE_ALL = -1;
181 public static final byte ERROR_NOTSUPPORTED = -1;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530182 public static final byte ATTRIBUTE_EQUALIZER = 1;
183 public static final byte ATTRIBUTE_REPEATMODE = 2;
184 public static final byte ATTRIBUTE_SHUFFLEMODE = 3;
185 public static final byte ATTRIBUTE_SCANMODE = 4;
186
187 private byte [] supportedAttributes = new byte[] {
188 ATTRIBUTE_REPEATMODE,
189 ATTRIBUTE_SHUFFLEMODE
190 };
191
192 public static final byte VALUE_REPEATMODE_OFF = 1;
193 public static final byte VALUE_REPEATMODE_SINGLE = 2;
194 public static final byte VALUE_REPEATMODE_ALL = 3;
195 public static final byte VALUE_REPEATMODE_GROUP = 4;
196
197 private byte [] supportedRepeatValues = new byte [] {
198 VALUE_REPEATMODE_OFF,
199 VALUE_REPEATMODE_SINGLE,
200 VALUE_REPEATMODE_ALL
201 };
202
203 public static final byte VALUE_SHUFFLEMODE_OFF = 1;
204 public static final byte VALUE_SHUFFLEMODE_ALL = 2;
205 public static final byte VALUE_SHUFFLEMODE_GROUP = 3;
206
207 private byte [] supportedShuffleValues = new byte [] {
208 VALUE_SHUFFLEMODE_OFF,
209 VALUE_SHUFFLEMODE_ALL
210 };
211
212 String [] AttrStr = new String[] {
213 "",
214 "Equalizer",
215 "Repeat Mode",
216 "Shuffle Mode",
217 "Scan Mode"
218 };
219
220 private byte [] unsupportedList = new byte [] {
221 0
222 };
223 private static final String EXTRA_GET_COMMAND = "commandExtra";
224 private static final String EXTRA_GET_RESPONSE = "Response";
225 private static final int GET_ATTRIBUTE_IDS = 0;
226 private static final int GET_VALUE_IDS = 1;
227 private static final int GET_ATTRIBUTE_TEXT = 2;
228 private static final int GET_VALUE_TEXT = 3;
229 private static final int GET_ATTRIBUTE_VALUES = 4;
230 private static final int NOTIFY_ATTRIBUTE_VALUES = 5;
rakesh reddyf032abb2014-09-24 15:47:45 +0530231 private static final int SET_ATTRIBUTE_VALUES = 6;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530232 private static final int GET_INVALID = 0xff;
233 private static final byte GET_ATTR_INVALID = 0x7f;
234
235 private static final String EXTRA_ATTRIBUTE_ID = "Attribute";
236 private static final String EXTRA_VALUE_STRING_ARRAY = "ValueStrings";
237 private static final String EXTRA_ATTRIB_VALUE_PAIRS = "AttribValuePairs";
238 private static final String EXTRA_ATTRIBUTE_STRING_ARRAY = "AttributeStrings";
239 private static final String EXTRA_VALUE_ID_ARRAY = "Values";
240 private static final String EXTRA_ATTIBUTE_ID_ARRAY = "Attributes";
241
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800242
The Android Open Source Project792a2202009-03-03 19:32:30 -0800243 private SharedPreferences mPreferences;
244 // We use this to distinguish between different cards when saving/restoring playlists.
245 // This will have to change if we want to support multiple simultaneous cards.
246 private int mCardId;
247
The Android Open Source Project490384b2009-03-11 12:11:59 -0700248 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800249
250 // interval after which we stop the service when idle
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700251 private static final int IDLE_DELAY = 60000;
Marco Nelissen8717d342011-09-08 09:32:51 -0700252
253 private RemoteControlClient mRemoteControlClient;
254
The Android Open Source Project792a2202009-03-03 19:32:30 -0800255 private Handler mMediaplayerHandler = new Handler() {
256 float mCurrentVolume = 1.0f;
257 @Override
258 public void handleMessage(Message msg) {
Marco Nelissen39888902010-03-02 10:27:05 -0800259 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800260 switch (msg.what) {
Marco Nelissen2da473d2010-09-29 16:11:12 -0700261 case FADEDOWN:
262 mCurrentVolume -= .05f;
263 if (mCurrentVolume > .2f) {
264 mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
265 } else {
266 mCurrentVolume = .2f;
267 }
268 mPlayer.setVolume(mCurrentVolume);
269 break;
270 case FADEUP:
271 mCurrentVolume += .01f;
272 if (mCurrentVolume < 1.0f) {
273 mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
274 } else {
275 mCurrentVolume = 1.0f;
276 }
277 mPlayer.setVolume(mCurrentVolume);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800278 break;
279 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700280 if (mIsSupposedToBePlaying) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800281 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800282 } else {
283 // the server died when we were idle, so just
284 // reopen the same song (it will start again
285 // from the beginning though when the user
286 // restarts)
Marco Nelissene41bd182012-03-14 08:24:40 -0700287 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800288 }
289 break;
Marco Nelissene41bd182012-03-14 08:24:40 -0700290 case TRACK_WENT_TO_NEXT:
291 mPlayPos = mNextPlayPos;
292 if (mCursor != null) {
293 mCursor.close();
294 mCursor = null;
295 }
kenthinea507512012-10-16 03:34:11 +0800296 if (mPlayPos >= 0 && mPlayPos < mPlayList.length) {
297 mCursor = getCursorForId(mPlayList[mPlayPos]);
298 }
Marco Nelissene41bd182012-03-14 08:24:40 -0700299 notifyChange(META_CHANGED);
300 updateNotification();
301 setNextTrack();
302 break;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800303 case TRACK_ENDED:
304 if (mRepeatMode == REPEAT_CURRENT) {
305 seek(0);
306 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800307 } else {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800308 gotoNext(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800309 }
310 break;
311 case RELEASE_WAKELOCK:
312 mWakeLock.release();
313 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800314
315 case FOCUSCHANGE:
316 // This code is here so we can better synchronize it with the code that
317 // handles fade-in
318 switch (msg.arg1) {
319 case AudioManager.AUDIOFOCUS_LOSS:
320 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
321 if(isPlaying()) {
322 mPausedByTransientLossOfFocus = false;
323 }
324 pause();
325 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800326 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800327 mMediaplayerHandler.removeMessages(FADEUP);
328 mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
329 break;
330 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Marco Nelissen7181da82010-12-01 16:39:04 -0800331 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
332 if(isPlaying()) {
333 mPausedByTransientLossOfFocus = true;
334 }
335 pause();
336 break;
337 case AudioManager.AUDIOFOCUS_GAIN:
338 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
339 if(!isPlaying() && mPausedByTransientLossOfFocus) {
340 mPausedByTransientLossOfFocus = false;
341 mCurrentVolume = 0f;
342 mPlayer.setVolume(mCurrentVolume);
343 play(); // also queues a fade-in
344 } else {
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800345 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -0800346 mMediaplayerHandler.sendEmptyMessage(FADEUP);
347 }
348 break;
349 default:
350 Log.e(LOGTAG, "Unknown audio focus change code");
351 }
352 break;
353
The Android Open Source Project792a2202009-03-03 19:32:30 -0800354 default:
355 break;
356 }
357 }
358 };
359
360 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
361 @Override
362 public void onReceive(Context context, Intent intent) {
363 String action = intent.getAction();
364 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800365 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800366 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800367 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800368 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
369 prev();
370 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
371 if (isPlaying()) {
372 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700373 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800374 } else {
375 play();
376 }
377 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
378 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700379 mPausedByTransientLossOfFocus = false;
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -0700380 } else if (CMDPLAY.equals(cmd)) {
381 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800382 } else if (CMDSTOP.equals(cmd)) {
383 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700384 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800385 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700386 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
387 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800388 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700389 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
390 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800391 }
392 }
393 };
394
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800395 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
Jean-Michel Trivif4cfdfd2010-03-31 12:12:23 -0700396 public void onAudioFocusChange(int focusChange) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800397 mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800398 }
399 };
400
The Android Open Source Project792a2202009-03-03 19:32:30 -0800401 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800402 }
403
404 @Override
405 public void onCreate() {
406 super.onCreate();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800407
408 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Marco Nelissen8717d342011-09-08 09:32:51 -0700409 ComponentName rec = new ComponentName(getPackageName(),
410 MediaButtonIntentReceiver.class.getName());
411 mAudioManager.registerMediaButtonEventReceiver(rec);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700412
413 Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
414 i.setComponent(rec);
415 PendingIntent pi = PendingIntent.getBroadcast(this /*context*/,
416 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/);
417 mRemoteControlClient = new RemoteControlClient(pi);
418 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
419
420 int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
421 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
422 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
423 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
424 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
425 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
426 mRemoteControlClient.setTransportControlFlags(flags);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800427
428 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800429 mCardId = MusicUtils.getCardId(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800430
431 registerExternalStorageListener();
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530432 registerA2dpServiceListener();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800433
434 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
435 mPlayer = new MultiPlayer();
436 mPlayer.setHandler(mMediaplayerHandler);
437
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530438 mSetBrowsedPlayerMonitor = new SetBrowsedPlayerMonitor();
439 mRemoteControlClient.setBrowsedPlayerUpdateListener(mSetBrowsedPlayerMonitor);
440
441 mSetPlayItemMonitor = new SetPlayItemMonitor();
442 mRemoteControlClient.setPlayItemListener(mSetPlayItemMonitor);
443
444 mGetNowPlayingEntriesMonitor = new GetNowPlayingEntriesMonitor();
445 mRemoteControlClient.setNowPlayingEntriesUpdateListener(mGetNowPlayingEntriesMonitor);
446
The Android Open Source Project792a2202009-03-03 19:32:30 -0800447 reloadQueue();
Marco Nelissen8717d342011-09-08 09:32:51 -0700448 notifyChange(QUEUE_CHANGED);
449 notifyChange(META_CHANGED);
450
rakesh reddyf032abb2014-09-24 15:47:45 +0530451 mAttributePairs.put(ATTRIBUTE_EQUALIZER, false);
452 mAttributePairs.put(ATTRIBUTE_REPEATMODE, true);
453 mAttributePairs.put(ATTRIBUTE_SHUFFLEMODE, true);
454
The Android Open Source Project792a2202009-03-03 19:32:30 -0800455 IntentFilter commandFilter = new IntentFilter();
456 commandFilter.addAction(SERVICECMD);
457 commandFilter.addAction(TOGGLEPAUSE_ACTION);
458 commandFilter.addAction(PAUSE_ACTION);
459 commandFilter.addAction(NEXT_ACTION);
460 commandFilter.addAction(PREVIOUS_ACTION);
461 registerReceiver(mIntentReceiver, commandFilter);
462
The Android Open Source Project792a2202009-03-03 19:32:30 -0800463 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
464 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
465 mWakeLock.setReferenceCounted(false);
466
467 // If the service was idle, but got killed before it stopped itself, the
468 // system will relaunch it. Make sure it gets stopped again in that case.
469 Message msg = mDelayedStopHandler.obtainMessage();
470 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
471 }
472
473 @Override
474 public void onDestroy() {
475 // Check that we're not being destroyed while something is still playing.
476 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800477 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800478 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700479 // release all MediaPlayer resources, including the native player and wakelocks
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700480 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
481 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
482 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
483 sendBroadcast(i);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700484 mPlayer.release();
485 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800486
487 mAudioManager.abandonAudioFocus(mAudioFocusListener);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700488 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800489
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700490 // make sure there aren't any other messages coming
491 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700492 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700493
The Android Open Source Project792a2202009-03-03 19:32:30 -0800494 if (mCursor != null) {
495 mCursor.close();
496 mCursor = null;
497 }
498
499 unregisterReceiver(mIntentReceiver);
500 if (mUnmountReceiver != null) {
501 unregisterReceiver(mUnmountReceiver);
502 mUnmountReceiver = null;
503 }
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530504
505 if (mA2dpReceiver != null) {
506 unregisterReceiver(mA2dpReceiver);
507 mA2dpReceiver = null;
508 }
509
rakesh reddyf032abb2014-09-24 15:47:45 +0530510 if (mAttributePairs != null) {
511 mAttributePairs.clear();
512 }
513
The Android Open Source Project792a2202009-03-03 19:32:30 -0800514 mWakeLock.release();
515 super.onDestroy();
516 }
517
518 private final char hexdigits [] = new char [] {
519 '0', '1', '2', '3',
520 '4', '5', '6', '7',
521 '8', '9', 'a', 'b',
522 'c', 'd', 'e', 'f'
523 };
524
525 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700526 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800527 return;
528 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700529
The Android Open Source Project792a2202009-03-03 19:32:30 -0800530 Editor ed = mPreferences.edit();
531 //long start = System.currentTimeMillis();
532 if (full) {
533 StringBuilder q = new StringBuilder();
534
535 // The current playlist is saved as a list of "reverse hexadecimal"
536 // numbers, which we can generate faster than normal decimal or
537 // hexadecimal numbers, which in turn allows us to save the playlist
538 // more often without worrying too much about performance.
539 // (saving the full state takes about 40 ms under no-load conditions
540 // on the phone)
541 int len = mPlayListLen;
542 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700543 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700544 if (n < 0) {
545 continue;
546 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800547 q.append("0;");
548 } else {
549 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700550 int digit = (int)(n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700551 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800552 q.append(hexdigits[digit]);
553 }
554 q.append(";");
555 }
556 }
557 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
558 ed.putString("queue", q.toString());
559 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700560 if (mShuffleMode != SHUFFLE_NONE) {
561 // In shuffle mode we need to save the history too
562 len = mHistory.size();
563 q.setLength(0);
564 for (int i = 0; i < len; i++) {
565 int n = mHistory.get(i);
566 if (n == 0) {
567 q.append("0;");
568 } else {
569 while (n != 0) {
570 int digit = (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700571 n >>>= 4;
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700572 q.append(hexdigits[digit]);
573 }
574 q.append(";");
575 }
576 }
577 ed.putString("history", q.toString());
578 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800579 }
580 ed.putInt("curpos", mPlayPos);
581 if (mPlayer.isInitialized()) {
582 ed.putLong("seekpos", mPlayer.position());
583 }
584 ed.putInt("repeatmode", mRepeatMode);
585 ed.putInt("shufflemode", mShuffleMode);
Brad Fitzpatrick14c3cae2010-09-10 09:38:57 -0700586 SharedPreferencesCompat.apply(ed);
587
The Android Open Source Project792a2202009-03-03 19:32:30 -0800588 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
589 }
590
591 private void reloadQueue() {
592 String q = null;
593
594 boolean newstyle = false;
595 int id = mCardId;
596 if (mPreferences.contains("cardid")) {
597 newstyle = true;
598 id = mPreferences.getInt("cardid", ~mCardId);
599 }
600 if (id == mCardId) {
601 // Only restore the saved playlist if the card is still
602 // the same one as when the playlist was saved
603 q = mPreferences.getString("queue", "");
604 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700605 int qlen = q != null ? q.length() : 0;
606 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800607 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700608 int plen = 0;
609 int n = 0;
610 int shift = 0;
611 for (int i = 0; i < qlen; i++) {
612 char c = q.charAt(i);
613 if (c == ';') {
614 ensurePlayListCapacity(plen + 1);
615 mPlayList[plen] = n;
616 plen++;
617 n = 0;
618 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800619 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700620 if (c >= '0' && c <= '9') {
621 n += ((c - '0') << shift);
622 } else if (c >= 'a' && c <= 'f') {
623 n += ((10 + c - 'a') << shift);
624 } else {
625 // bogus playlist data
626 plen = 0;
627 break;
628 }
629 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800630 }
631 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700632 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800633
634 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700635 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800636 // The saved playlist is bogus, discard it
637 mPlayListLen = 0;
638 return;
639 }
640 mPlayPos = pos;
641
642 // When reloadQueue is called in response to a card-insertion,
643 // we might not be able to query the media provider right away.
644 // To deal with this, try querying for the current file, and if
645 // that fails, wait a while and try again. If that too fails,
646 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700647 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800648 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
649 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700650 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800651 // wait a bit and try again
652 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700653 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800654 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
655 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
656 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700657 if (crsr != null) {
658 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800659 }
660
661 // Make sure we don't auto-skip to the next song, since that
662 // also starts playback. What could happen in that case is:
663 // - music is paused
664 // - go to UMS and delete some files, including the currently playing one
665 // - come back from UMS
666 // (time passes)
667 // - music app is killed for some reason (out of memory)
668 // - music service is restarted, service restores state, doesn't find
669 // the "current" file, goes to the next and: playback starts on its
670 // own, potentially at some random inconvenient time.
671 mOpenFailedCounter = 20;
672 mQuietMode = true;
Marco Nelissene41bd182012-03-14 08:24:40 -0700673 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800674 mQuietMode = false;
675 if (!mPlayer.isInitialized()) {
676 // couldn't restore the saved state
677 mPlayListLen = 0;
678 return;
679 }
680
681 long seekpos = mPreferences.getLong("seekpos", 0);
682 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Marco Nelissene99341f2009-11-11 11:13:51 -0800683 Log.d(LOGTAG, "restored queue, currently at position "
684 + position() + "/" + duration()
685 + " (requested " + seekpos + ")");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800686
687 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
688 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
689 repmode = REPEAT_NONE;
690 }
691 mRepeatMode = repmode;
692
693 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
694 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
695 shufmode = SHUFFLE_NONE;
696 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700697 if (shufmode != SHUFFLE_NONE) {
698 // in shuffle mode we need to restore the history too
699 q = mPreferences.getString("history", "");
700 qlen = q != null ? q.length() : 0;
701 if (qlen > 1) {
702 plen = 0;
703 n = 0;
704 shift = 0;
705 mHistory.clear();
706 for (int i = 0; i < qlen; i++) {
707 char c = q.charAt(i);
708 if (c == ';') {
709 if (n >= mPlayListLen) {
710 // bogus history data
711 mHistory.clear();
712 break;
713 }
714 mHistory.add(n);
715 n = 0;
716 shift = 0;
717 } else {
718 if (c >= '0' && c <= '9') {
719 n += ((c - '0') << shift);
720 } else if (c >= 'a' && c <= 'f') {
721 n += ((10 + c - 'a') << shift);
722 } else {
723 // bogus history data
724 mHistory.clear();
725 break;
726 }
727 shift += 4;
728 }
729 }
730 }
731 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800732 if (shufmode == SHUFFLE_AUTO) {
733 if (! makeAutoShuffleList()) {
734 shufmode = SHUFFLE_NONE;
735 }
736 }
737 mShuffleMode = shufmode;
738 }
739 }
740
741 @Override
742 public IBinder onBind(Intent intent) {
743 mDelayedStopHandler.removeCallbacksAndMessages(null);
744 mServiceInUse = true;
745 return mBinder;
746 }
747
748 @Override
749 public void onRebind(Intent intent) {
750 mDelayedStopHandler.removeCallbacksAndMessages(null);
751 mServiceInUse = true;
752 }
753
754 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700755 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800756 mServiceStartId = startId;
757 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700758
759 if (intent != null) {
760 String action = intent.getAction();
761 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800762 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700763
764 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800765 gotoNext(true);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700766 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800767 if (position() < 2000) {
768 prev();
769 } else {
770 seek(0);
771 play();
772 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700773 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
774 if (isPlaying()) {
775 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700776 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700777 } else {
778 play();
779 }
780 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800781 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700782 mPausedByTransientLossOfFocus = false;
Jaikumar Ganesh47dd8472010-12-16 15:51:31 -0800783 } else if (CMDPLAY.equals(cmd)) {
784 play();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700785 } else if (CMDSTOP.equals(cmd)) {
786 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700787 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700788 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800789 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800790 }
791
792 // make sure the service will shut down on its own if it was
793 // just started but not bound to and nothing is playing
794 mDelayedStopHandler.removeCallbacksAndMessages(null);
795 Message msg = mDelayedStopHandler.obtainMessage();
796 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700797 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800798 }
799
800 @Override
801 public boolean onUnbind(Intent intent) {
802 mServiceInUse = false;
803
804 // Take a snapshot of the current playlist
805 saveQueue(true);
806
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700807 if (isPlaying() || mPausedByTransientLossOfFocus) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800808 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700809 // an in-progress action requesting audio focus ends, so don't stop the service now.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800810 return true;
811 }
812
813 // If there is a playlist but playback is paused, then wait a while
814 // before stopping the service, so that pause/resume isn't slow.
815 // Also delay stopping the service if we're transitioning between tracks.
816 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
817 Message msg = mDelayedStopHandler.obtainMessage();
818 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
819 return true;
820 }
821
822 // No active playlist, OK to stop the service right now
823 stopSelf(mServiceStartId);
824 return true;
825 }
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530826
827 private void getNowPlayingEntries() {
828 Log.i(LOGTAG, "getNowPlayingEntries: num of items: " + mPlayListLen);
Gaurav Asati3996dca2014-11-21 16:26:58 +0530829 if (mPlayList == null) {
830 Log.i(LOGTAG,"getNowPlayingEntries: mPlayListLen is null");
831 return;
832 }
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530833 synchronized (mPlayList) {
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +0530834 long [] nowPlayingList = new long[mPlayListLen];
835 for (int count = 0; count < mPlayListLen; count++) {
836 nowPlayingList[count] = mPlayList[count];
837 }
838 mRemoteControlClient.updateNowPlayingEntries(nowPlayingList);
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530839 }
840 }
841
842 private void setBrowsedPlayer() {
843 Log.i(LOGTAG, "setBrowsedPlayer");
844 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
845 Log.i(LOGTAG, "URI: " + uri);
846 mRemoteControlClient.updateFolderInfoBrowsedPlayer(uri.toString());
847 }
848
849 private void playItem(int scope, long playItemUid) {
850 boolean success = false;
851 Log.i(LOGTAG, "playItem uid: " + playItemUid + " scope: " + scope);
852 if (playItemUid < 0) {
853 mRemoteControlClient.playItemResponse(success);
854 return;
855 } else if (scope == SCOPE_FILE_SYSTEM) {
856 success = openItem(playItemUid);
857 } else if (scope == SCOPE_NOW_PLAYING) {
858 for (int index = 0; index < mPlayListLen; index++) {
859 if (mPlayList[index] == playItemUid) {
860 Log.i(LOGTAG, "Now Playing list contains UID at " + index);
861 success = true;
862 break;
863 }
864 }
865 if (success) {
866 success = openItem(playItemUid);
867 }
868 }
869 mRemoteControlClient.playItemResponse(success);
870 }
871
872 private boolean openItem (long playItemUid) {
873 boolean isSuccess = false;
874 if (mCursor != null) {
875 mCursor.close();
876 mCursor = null;
877 }
878 if (mPlayListLen == 0) {
879 Log.e(LOGTAG, "Playlist Length = 0");
880 return isSuccess;
881 }
882 stop(false);
883 mCursor = getCursorForId(playItemUid);
884 if (mCursor != null) {
885 long [] list = new long[] { playItemUid };
886 enqueue(list, NOW);
887 Log.i(LOGTAG, "Opened UID: " + playItemUid);
888 isSuccess = true;
889 } else {
890 Log.e(LOGTAG, "Cursor could not be fetched");
891 }
892 return isSuccess;
893 }
894
The Android Open Source Project792a2202009-03-03 19:32:30 -0800895 private Handler mDelayedStopHandler = new Handler() {
896 @Override
897 public void handleMessage(Message msg) {
898 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700899 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800900 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
901 return;
902 }
903 // save the queue again, because it might have changed
904 // since the user exited the music app (because of
905 // party-shuffle or because the play-position changed)
906 saveQueue(true);
907 stopSelf(mServiceStartId);
908 }
909 };
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700910
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530911 private Handler mAvrcpHandler = new Handler() {
912 @Override
913 public void handleMessage(Message msg) {
914 switch (msg.what) {
915 case SET_BROWSED_PLAYER:
916 setBrowsedPlayer();
917 break;
918 case SET_PLAY_ITEM:
919 playItem(msg.arg1, ((Long)msg.obj).longValue());
920 break;
921 case GET_NOW_PLAYING_ENTRIES:
922 getNowPlayingEntries();
923 break;
924 default:
925 }
926 }
927 };
928
The Android Open Source Project792a2202009-03-03 19:32:30 -0800929 /**
930 * Called when we receive a ACTION_MEDIA_EJECT notification.
931 *
932 * @param storagePath path to mount point for the removed media
933 */
934 public void closeExternalStorageFiles(String storagePath) {
935 // stop playback and clean up if the SD card is going to be unmounted.
936 stop(true);
937 notifyChange(QUEUE_CHANGED);
938 notifyChange(META_CHANGED);
939 }
940
941 /**
942 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
943 * The intent will call closeExternalStorageFiles() if the external media
944 * is going to be ejected, so applications can clean up any files they have open.
945 */
946 public void registerExternalStorageListener() {
947 if (mUnmountReceiver == null) {
948 mUnmountReceiver = new BroadcastReceiver() {
949 @Override
950 public void onReceive(Context context, Intent intent) {
951 String action = intent.getAction();
952 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
953 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700954 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800955 closeExternalStorageFiles(intent.getData().getPath());
956 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
957 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800958 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800959 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700960 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800961 notifyChange(QUEUE_CHANGED);
962 notifyChange(META_CHANGED);
963 }
964 }
965 };
966 IntentFilter iFilter = new IntentFilter();
967 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
968 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
969 iFilter.addDataScheme("file");
970 registerReceiver(mUnmountReceiver, iFilter);
971 }
972 }
973
Ayan Ghosh6ceaa692014-07-23 21:03:40 +0530974 public void registerA2dpServiceListener() {
975 mA2dpReceiver = new BroadcastReceiver() {
976 @Override
977 public void onReceive(Context context, Intent intent) {
978 String action = intent.getAction();
979 String cmd = intent.getStringExtra("command");
980 if (action.equals(SET_ADDRESSED_PLAYER)) {
981 play(); // this ensures audio focus change is called and play the media
982 } else if (action.equals(PLAYSTATUS_REQUEST)) {
983 notifyChange(PLAYSTATUS_RESPONSE);
984 } else if (PLAYERSETTINGS_REQUEST.equals(action)) {
985 if (CMDGET.equals(cmd)) {
986 int getCommand = intent.getIntExtra(EXTRA_GET_COMMAND,
987 GET_INVALID);
988 byte attribute;
989 byte [] attrIds; byte [] valIds;
990 switch (getCommand) {
991 case GET_ATTRIBUTE_IDS:
992 notifyAttributeIDs(PLAYERSETTINGS_RESPONSE);
993 break;
994 case GET_VALUE_IDS:
995 attribute =
996 intent.getByteExtra(EXTRA_ATTRIBUTE_ID,
997 GET_ATTR_INVALID);
998 notifyValueIDs(PLAYERSETTINGS_RESPONSE, attribute);
999 break;
1000 case GET_ATTRIBUTE_TEXT:
1001 attrIds = intent.getByteArrayExtra(
1002 EXTRA_ATTIBUTE_ID_ARRAY);
1003 notifyAttributesText(PLAYERSETTINGS_RESPONSE, attrIds);
1004 break;
1005 case GET_VALUE_TEXT:
1006 attribute =
1007 intent.getByteExtra(EXTRA_ATTRIBUTE_ID,
1008 GET_ATTR_INVALID);
1009 valIds = intent.getByteArrayExtra(
1010 EXTRA_VALUE_ID_ARRAY);
1011 notifyAttributeValuesText(
1012 PLAYERSETTINGS_RESPONSE, attribute, valIds);
1013 break;
1014 case GET_ATTRIBUTE_VALUES:
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301015 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05301016 mAttributePairs, GET_ATTRIBUTE_VALUES);
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301017 break;
1018 default:
1019 Log.e(LOGTAG, "invalid getCommand"+getCommand);
1020 break;
1021 }
1022 } else if (CMDSET.equals(cmd)){
1023 byte[] attribValuePairs = intent.getByteArrayExtra(
1024 EXTRA_ATTRIB_VALUE_PAIRS);
1025 setValidAttributes(attribValuePairs);
1026 }
1027 }
1028 }
1029 };
1030 IntentFilter iFilter = new IntentFilter();
1031 iFilter.addAction(SET_ADDRESSED_PLAYER);
1032 iFilter.addAction(PLAYSTATUS_REQUEST);
1033 iFilter.addAction(PLAYERSETTINGS_REQUEST);
1034 registerReceiver(mA2dpReceiver, iFilter);
1035 }
1036
The Android Open Source Project792a2202009-03-03 19:32:30 -08001037 /**
1038 * Notify the change-receivers that something has changed.
1039 * The intent that is sent contains the following data
1040 * for the currently playing track:
1041 * "id" - Integer: the database row ID
1042 * "artist" - String: the name of the artist
1043 * "album" - String: the name of the album
1044 * "track" - String: the name of the track
1045 * The intent has an action that is one of
1046 * "com.android.music.metachanged"
1047 * "com.android.music.queuechanged",
1048 * "com.android.music.playbackcomplete"
1049 * "com.android.music.playstatechanged"
1050 * respectively indicating that a new track has
1051 * started playing, that the playback queue has
1052 * changed, that playback has stopped because
1053 * the last file in the list has been played,
1054 * or that the play-state changed (paused/resumed).
1055 */
1056 private void notifyChange(String what) {
Marco Nelissen8717d342011-09-08 09:32:51 -07001057
The Android Open Source Project792a2202009-03-03 19:32:30 -08001058 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -07001059 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001060 i.putExtra("artist", getArtistName());
1061 i.putExtra("album",getAlbumName());
1062 i.putExtra("track", getTrackName());
Marco Nelissen6b507de2010-10-20 14:08:10 -07001063 i.putExtra("playing", isPlaying());
1064 sendStickyBroadcast(i);
Marco Nelissen8717d342011-09-08 09:32:51 -07001065
1066 if (what.equals(PLAYSTATE_CHANGED)) {
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301067 mRemoteControlClient.setPlaybackState((isPlaying() ?
1068 RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED),
1069 position() , RemoteControlClient.PLAYBACK_SPEED_1X);
Marco Nelissen8717d342011-09-08 09:32:51 -07001070 } else if (what.equals(META_CHANGED)) {
Mike Lockwood8ef36792013-08-01 10:03:03 -07001071 RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
1072 ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
1073 ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
rakesh reddyf5e0fc82014-10-08 20:17:39 +05301074 ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
Mike Lockwood8ef36792013-08-01 10:03:03 -07001075 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301076 if ((mPlayList != null) && (mPlayPos >= 0) && (mPlayPos < mPlayList.length)) {
1077 ed.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
1078 mPlayList[mPlayPos]);
1079 } else {
1080 ed.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
1081 INVALID_SONG_UID);
1082 }
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05301083 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, mPlayPos);
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301084 try {
1085 ed.putLong(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS, mPlayListLen);
1086 } catch (IllegalArgumentException e) {
1087 Log.e(LOGTAG, "METADATA_KEY_NUM_TRACKS: failed: " + e);
1088 }
Mike Lockwood8ef36792013-08-01 10:03:03 -07001089 Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
1090 if (b != null) {
1091 ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
1092 }
1093 ed.apply();
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301094 } else if (what.equals(QUEUE_CHANGED)) {
1095 mRemoteControlClient.updateNowPlayingContentChange();
Marco Nelissen8717d342011-09-08 09:32:51 -07001096 }
1097
The Android Open Source Project792a2202009-03-03 19:32:30 -08001098 if (what.equals(QUEUE_CHANGED)) {
1099 saveQueue(true);
1100 } else {
1101 saveQueue(false);
1102 }
1103
The Android Open Source Project490384b2009-03-11 12:11:59 -07001104 // Share this notification directly with our widgets
1105 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001106 }
1107
1108 private void ensurePlayListCapacity(int size) {
1109 if (mPlayList == null || size > mPlayList.length) {
1110 // reallocate at 2x requested size so we don't
1111 // need to grow and copy the array for every
1112 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -07001113 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -07001114 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001115 for (int i = 0; i < len; i++) {
1116 newlist[i] = mPlayList[i];
1117 }
1118 mPlayList = newlist;
1119 }
1120 // FIXME: shrink the array when the needed size is much smaller
1121 // than the allocated size
1122 }
1123
1124 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -07001125 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001126 int addlen = list.length;
1127 if (position < 0) { // overwrite
1128 mPlayListLen = 0;
1129 position = 0;
1130 }
1131 ensurePlayListCapacity(mPlayListLen + addlen);
1132 if (position > mPlayListLen) {
1133 position = mPlayListLen;
1134 }
1135
1136 // move part of list after insertion point
1137 int tailsize = mPlayListLen - position;
1138 for (int i = tailsize ; i > 0 ; i--) {
1139 mPlayList[position + i] = mPlayList[position + i - addlen];
1140 }
1141
1142 // copy list into playlist
1143 for (int i = 0; i < addlen; i++) {
1144 mPlayList[position + i] = list[i];
1145 }
1146 mPlayListLen += addlen;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001147 if (mPlayListLen == 0) {
1148 mCursor.close();
1149 mCursor = null;
1150 notifyChange(META_CHANGED);
1151 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001152 }
1153
1154 /**
1155 * Appends a list of tracks to the current playlist.
1156 * If nothing is playing currently, playback will be started at
1157 * the first track.
1158 * If the action is NOW, playback will switch to the first of
1159 * the new tracks immediately.
1160 * @param list The list of tracks to append.
1161 * @param action NOW, NEXT or LAST
1162 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001163 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001164 synchronized(this) {
1165 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
1166 addToPlayList(list, mPlayPos + 1);
1167 notifyChange(QUEUE_CHANGED);
1168 } else {
1169 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
1170 addToPlayList(list, Integer.MAX_VALUE);
1171 notifyChange(QUEUE_CHANGED);
1172 if (action == NOW) {
1173 mPlayPos = mPlayListLen - list.length;
Marco Nelissene41bd182012-03-14 08:24:40 -07001174 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001175 play();
1176 notifyChange(META_CHANGED);
1177 return;
1178 }
1179 }
1180 if (mPlayPos < 0) {
1181 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001182 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001183 play();
1184 notifyChange(META_CHANGED);
1185 }
1186 }
1187 }
1188
1189 /**
1190 * Replaces the current playlist with a new list,
1191 * and prepares for starting playback at the specified
1192 * position in the list, or a random position if the
1193 * specified position is 0.
1194 * @param list The new list of tracks.
1195 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001196 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001197 synchronized (this) {
1198 if (mShuffleMode == SHUFFLE_AUTO) {
1199 mShuffleMode = SHUFFLE_NORMAL;
1200 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001201 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001202 int listlength = list.length;
1203 boolean newlist = true;
1204 if (mPlayListLen == listlength) {
1205 // possible fast path: list might be the same
1206 newlist = false;
1207 for (int i = 0; i < listlength; i++) {
1208 if (list[i] != mPlayList[i]) {
1209 newlist = true;
1210 break;
1211 }
1212 }
1213 }
1214 if (newlist) {
1215 addToPlayList(list, -1);
1216 notifyChange(QUEUE_CHANGED);
1217 }
1218 int oldpos = mPlayPos;
1219 if (position >= 0) {
1220 mPlayPos = position;
1221 } else {
1222 mPlayPos = mRand.nextInt(mPlayListLen);
1223 }
1224 mHistory.clear();
1225
1226 saveBookmarkIfNeeded();
Marco Nelissene41bd182012-03-14 08:24:40 -07001227 openCurrentAndNext();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -07001228 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001229 notifyChange(META_CHANGED);
1230 }
1231 }
1232 }
1233
1234 /**
1235 * Moves the item at index1 to index2.
1236 * @param index1
1237 * @param index2
1238 */
1239 public void moveQueueItem(int index1, int index2) {
1240 synchronized (this) {
1241 if (index1 >= mPlayListLen) {
1242 index1 = mPlayListLen - 1;
1243 }
1244 if (index2 >= mPlayListLen) {
1245 index2 = mPlayListLen - 1;
1246 }
1247 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001248 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001249 for (int i = index1; i < index2; i++) {
1250 mPlayList[i] = mPlayList[i+1];
1251 }
1252 mPlayList[index2] = tmp;
1253 if (mPlayPos == index1) {
1254 mPlayPos = index2;
1255 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
1256 mPlayPos--;
1257 }
1258 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001259 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001260 for (int i = index1; i > index2; i--) {
1261 mPlayList[i] = mPlayList[i-1];
1262 }
1263 mPlayList[index2] = tmp;
1264 if (mPlayPos == index1) {
1265 mPlayPos = index2;
1266 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
1267 mPlayPos++;
1268 }
1269 }
1270 notifyChange(QUEUE_CHANGED);
1271 }
1272 }
1273
1274 /**
1275 * Returns the current play list
1276 * @return An array of integers containing the IDs of the tracks in the play list
1277 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001278 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001279 synchronized (this) {
1280 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001281 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001282 for (int i = 0; i < len; i++) {
1283 list[i] = mPlayList[i];
1284 }
1285 return list;
1286 }
1287 }
1288
Marco Nelissene41bd182012-03-14 08:24:40 -07001289 private Cursor getCursorForId(long lid) {
1290 String id = String.valueOf(lid);
1291
1292 Cursor c = getContentResolver().query(
1293 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1294 mCursorCols, "_id=" + id , null, null);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001295 if (c != null) {
1296 c.moveToFirst();
1297 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001298 return c;
1299 }
1300
1301 private void openCurrentAndNext() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001302 synchronized (this) {
1303 if (mCursor != null) {
1304 mCursor.close();
1305 mCursor = null;
1306 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001307
The Android Open Source Project792a2202009-03-03 19:32:30 -08001308 if (mPlayListLen == 0) {
1309 return;
1310 }
1311 stop(false);
1312
Marco Nelissen90d1f622012-04-05 12:27:56 -07001313 mCursor = getCursorForId(mPlayList[mPlayPos]);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001314 while(true) {
1315 if (mCursor != null && mCursor.getCount() != 0 &&
1316 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" +
1317 mCursor.getLong(IDCOLIDX))) {
1318 break;
Marco Nelissen6fb85512012-07-18 07:46:21 -07001319 }
Marco Nelissenc37b2002012-10-16 12:59:54 -07001320 // if we get here then opening the file failed. We can close the cursor now, because
1321 // we're either going to create a new one next, or stop trying
1322 if (mCursor != null) {
1323 mCursor.close();
1324 mCursor = null;
1325 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001326 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1327 int pos = getNextPosition(false);
1328 if (pos < 0) {
1329 gotoIdleState();
1330 if (mIsSupposedToBePlaying) {
1331 mIsSupposedToBePlaying = false;
1332 notifyChange(PLAYSTATE_CHANGED);
1333 }
1334 return;
1335 }
1336 mPlayPos = pos;
1337 stop(false);
1338 mPlayPos = pos;
1339 mCursor = getCursorForId(mPlayList[mPlayPos]);
1340 } else {
1341 mOpenFailedCounter = 0;
1342 if (!mQuietMode) {
1343 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1344 }
1345 Log.d(LOGTAG, "Failed to open file for playback");
Marco Nelissenc37b2002012-10-16 12:59:54 -07001346 gotoIdleState();
1347 if (mIsSupposedToBePlaying) {
1348 mIsSupposedToBePlaying = false;
1349 notifyChange(PLAYSTATE_CHANGED);
1350 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001351 return;
Marco Nelissene41bd182012-03-14 08:24:40 -07001352 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001353 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001354
1355 // go to bookmark if needed
1356 if (isPodcast()) {
1357 long bookmark = getBookmark();
1358 // Start playing a little bit before the bookmark,
1359 // so it's easier to get back in to the narrative.
1360 seek(bookmark - 5000);
1361 }
1362 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001363 }
1364 }
1365
Marco Nelissene41bd182012-03-14 08:24:40 -07001366 private void setNextTrack() {
Preetam Singh Ranawat130880a2013-11-27 04:01:40 +05301367 if(!SystemProperties.getBoolean("audio.gapless.playback.disable", false)) {
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301368 mNextPlayPos = getNextPosition(false);
1369 if (mNextPlayPos >= 0) {
1370 long id = mPlayList[mNextPlayPos];
1371 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1372 } else {
1373 mPlayer.setNextDataSource(null);
1374 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001375 }
1376 }
1377
The Android Open Source Project792a2202009-03-03 19:32:30 -08001378 /**
1379 * Opens the specified file and readies it for playback.
1380 *
1381 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001382 */
Marco Nelissen90d1f622012-04-05 12:27:56 -07001383 public boolean open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001384 synchronized (this) {
1385 if (path == null) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001386 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001387 }
1388
The Android Open Source Project792a2202009-03-03 19:32:30 -08001389 // if mCursor is null, try to associate path with a database cursor
1390 if (mCursor == null) {
1391
1392 ContentResolver resolver = getContentResolver();
1393 Uri uri;
1394 String where;
1395 String selectionArgs[];
1396 if (path.startsWith("content://media/")) {
1397 uri = Uri.parse(path);
1398 where = null;
1399 selectionArgs = null;
1400 } else {
1401 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1402 where = MediaStore.Audio.Media.DATA + "=?";
1403 selectionArgs = new String[] { path };
1404 }
1405
1406 try {
1407 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1408 if (mCursor != null) {
1409 if (mCursor.getCount() == 0) {
1410 mCursor.close();
1411 mCursor = null;
1412 } else {
1413 mCursor.moveToNext();
1414 ensurePlayListCapacity(1);
1415 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001416 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001417 mPlayPos = 0;
1418 }
1419 }
1420 } catch (UnsupportedOperationException ex) {
1421 }
1422 }
1423 mFileToPlay = path;
1424 mPlayer.setDataSource(mFileToPlay);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001425 if (mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001426 mOpenFailedCounter = 0;
Marco Nelissen90d1f622012-04-05 12:27:56 -07001427 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001428 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001429 stop(true);
1430 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001431 }
1432 }
1433
1434 /**
1435 * Starts playback of a previously opened file.
1436 */
1437 public void play() {
yanmei.zhang81deb3d2015-06-08 19:54:48 +08001438 TelephonyManager tel = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
1439 if (TelephonyManager.CALL_STATE_IDLE == tel.getCallState()) {
1440 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1441 AudioManager.AUDIOFOCUS_GAIN);
1442 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1443 MediaButtonIntentReceiver.class.getName()));
1444 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001445
yanmei.zhang81deb3d2015-06-08 19:54:48 +08001446 if (mPlayer.isInitialized()) {
1447 mPlayer.start();
1448 // make sure we fade in, in case a previous fadein was stopped because
1449 // of another focus loss
1450 mMediaplayerHandler.removeMessages(FADEDOWN);
1451 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001452
yanmei.zhang81deb3d2015-06-08 19:54:48 +08001453 updateNotification();
1454 if (!mIsSupposedToBePlaying) {
1455 mIsSupposedToBePlaying = true;
1456 notifyChange(PLAYSTATE_CHANGED);
1457 }
Mike Cleron347fe572009-10-09 12:26:28 -07001458
yanmei.zhang81deb3d2015-06-08 19:54:48 +08001459 } else if (mPlayListLen <= 0) {
1460 // This is mostly so that if you press 'play' on a bluetooth headset
1461 // without every having played anything before, it will still play
1462 // something.
1463 setShuffleMode(SHUFFLE_AUTO);
1464 }
1465 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001466 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001467
1468 private void updateNotification() {
1469 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1470 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1471 if (getAudioId() < 0) {
1472 // streaming
1473 views.setTextViewText(R.id.trackname, getPath());
1474 views.setTextViewText(R.id.artistalbum, null);
1475 } else {
1476 String artist = getArtistName();
1477 views.setTextViewText(R.id.trackname, getTrackName());
1478 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1479 artist = getString(R.string.unknown_artist_name);
1480 }
1481 String album = getAlbumName();
1482 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1483 album = getString(R.string.unknown_album_name);
1484 }
1485
1486 views.setTextViewText(R.id.artistalbum,
1487 getString(R.string.notification_artist_album, artist, album)
1488 );
1489 }
1490 Notification status = new Notification();
1491 status.contentView = views;
1492 status.flags |= Notification.FLAG_ONGOING_EVENT;
1493 status.icon = R.drawable.stat_notify_musicplayer;
1494 status.contentIntent = PendingIntent.getActivity(this, 0,
1495 new Intent("com.android.music.PLAYBACK_VIEWER")
1496 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1497 startForeground(PLAYBACKSERVICE_STATUS, status);
1498 }
1499
The Android Open Source Project792a2202009-03-03 19:32:30 -08001500 private void stop(boolean remove_status_icon) {
Glenn Kastenb1c285c2012-11-07 15:44:01 -08001501 if (mPlayer != null && mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001502 mPlayer.stop();
1503 }
1504 mFileToPlay = null;
1505 if (mCursor != null) {
1506 mCursor.close();
1507 mCursor = null;
1508 }
1509 if (remove_status_icon) {
1510 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001511 } else {
1512 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001513 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001514 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001515 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001516 }
1517 }
1518
1519 /**
1520 * Stops playback.
1521 */
1522 public void stop() {
1523 stop(true);
1524 }
1525
1526 /**
1527 * Pauses playback (call play() to resume)
1528 */
1529 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001530 synchronized(this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001531 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001532 if (isPlaying()) {
1533 mPlayer.pause();
1534 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001535 mIsSupposedToBePlaying = false;
1536 notifyChange(PLAYSTATE_CHANGED);
1537 saveBookmarkIfNeeded();
1538 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001539 }
1540 }
1541
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001542 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001543 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001544 * @return true if something is playing (or will be playing shortly, in case
1545 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001546 */
1547 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001548 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001549 }
1550
1551 /*
1552 Desired behavior for prev/next/shuffle:
1553
1554 - NEXT will move to the next track in the list when not shuffling, and to
1555 a track randomly picked from the not-yet-played tracks when shuffling.
1556 If all tracks have already been played, pick from the full set, but
1557 avoid picking the previously played track if possible.
1558 - when shuffling, PREV will go to the previously played track. Hitting PREV
1559 again will go to the track played before that, etc. When the start of the
1560 history has been reached, PREV is a no-op.
1561 When not shuffling, PREV will go to the sequentially previous track (the
1562 difference with the shuffle-case is mainly that when not shuffling, the
1563 user can back up to tracks that are not in the history).
1564
1565 Example:
1566 When playing an album with 10 tracks from the start, and enabling shuffle
1567 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1568 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1569 When hitting 'prev' 8 times while playing track 7 in this example, the
1570 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1571 a random track will be picked again. If at any time user disables shuffling
1572 the next/previous track will be picked in sequential order again.
1573 */
1574
1575 public void prev() {
1576 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001577 if (mShuffleMode == SHUFFLE_NORMAL) {
1578 // go to previously-played track and remove it from the history
1579 int histsize = mHistory.size();
1580 if (histsize == 0) {
1581 // prev is a no-op
1582 return;
1583 }
1584 Integer pos = mHistory.remove(histsize - 1);
1585 mPlayPos = pos.intValue();
1586 } else {
1587 if (mPlayPos > 0) {
1588 mPlayPos--;
1589 } else {
1590 mPlayPos = mPlayListLen - 1;
1591 }
1592 }
1593 saveBookmarkIfNeeded();
1594 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001595 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001596 play();
1597 notifyChange(META_CHANGED);
1598 }
1599 }
1600
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001601 /**
1602 * Get the next position to play. Note that this may actually modify mPlayPos
1603 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1604 * be adjusted. Either way, the return value is the next value that should be
1605 * assigned to mPlayPos;
1606 */
1607 private int getNextPosition(boolean force) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001608 if (mRepeatMode == REPEAT_CURRENT) {
gaoyl1997c664b672015-05-27 15:53:02 +08001609 if (!force) {
1610 return mPlayPos;
1611 } else if ((mPlayPos < 0) || (mPlayPos >= mPlayListLen - 1)) {
1612 return 0;
1613 } else {
1614 return mPlayPos + 1;
1615 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001616 } else if (mShuffleMode == SHUFFLE_NORMAL) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001617 // Pick random next track from the not-yet-played ones
1618 // TODO: make it work right after adding/removing items in the queue.
1619
1620 // Store the current file in the history, but keep the history at a
1621 // reasonable size
1622 if (mPlayPos >= 0) {
1623 mHistory.add(mPlayPos);
1624 }
1625 if (mHistory.size() > MAX_HISTORY_SIZE) {
1626 mHistory.removeElementAt(0);
1627 }
1628
1629 int numTracks = mPlayListLen;
1630 int[] tracks = new int[numTracks];
1631 for (int i=0;i < numTracks; i++) {
1632 tracks[i] = i;
1633 }
1634
1635 int numHistory = mHistory.size();
1636 int numUnplayed = numTracks;
1637 for (int i=0;i < numHistory; i++) {
1638 int idx = mHistory.get(i).intValue();
1639 if (idx < numTracks && tracks[idx] >= 0) {
1640 numUnplayed--;
1641 tracks[idx] = -1;
1642 }
1643 }
1644
1645 // 'numUnplayed' now indicates how many tracks have not yet
1646 // been played, and 'tracks' contains the indices of those
1647 // tracks.
1648 if (numUnplayed <=0) {
1649 // everything's already been played
1650 if (mRepeatMode == REPEAT_ALL || force) {
1651 //pick from full set
1652 numUnplayed = numTracks;
1653 for (int i=0;i < numTracks; i++) {
1654 tracks[i] = i;
1655 }
1656 } else {
1657 // all done
1658 return -1;
1659 }
1660 }
1661 int skip = mRand.nextInt(numUnplayed);
1662 int cnt = -1;
1663 while (true) {
1664 while (tracks[++cnt] < 0)
1665 ;
1666 skip--;
1667 if (skip < 0) {
1668 break;
1669 }
1670 }
1671 return cnt;
1672 } else if (mShuffleMode == SHUFFLE_AUTO) {
1673 doAutoShuffleUpdate();
1674 return mPlayPos + 1;
1675 } else {
1676 if (mPlayPos >= mPlayListLen - 1) {
1677 // we're at the end of the list
1678 if (mRepeatMode == REPEAT_NONE && !force) {
1679 // all done
1680 return -1;
1681 } else if (mRepeatMode == REPEAT_ALL || force) {
1682 return 0;
1683 }
1684 return -1;
1685 } else {
1686 return mPlayPos + 1;
1687 }
1688 }
1689 }
1690
1691 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001692 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001693 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001694 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001695 return;
1696 }
1697
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001698 int pos = getNextPosition(force);
1699 if (pos < 0) {
1700 gotoIdleState();
1701 if (mIsSupposedToBePlaying) {
1702 mIsSupposedToBePlaying = false;
1703 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001704 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001705 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001706 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001707 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001708 saveBookmarkIfNeeded();
1709 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001710 mPlayPos = pos;
1711 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001712 play();
1713 notifyChange(META_CHANGED);
1714 }
1715 }
1716
1717 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001718 mDelayedStopHandler.removeCallbacksAndMessages(null);
1719 Message msg = mDelayedStopHandler.obtainMessage();
1720 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001721 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001722 }
1723
1724 private void saveBookmarkIfNeeded() {
1725 try {
1726 if (isPodcast()) {
1727 long pos = position();
1728 long bookmark = getBookmark();
1729 long duration = duration();
1730 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1731 (pos > bookmark && (pos - 10000) < bookmark)) {
1732 // The existing bookmark is close to the current
1733 // position, so don't update it.
1734 return;
1735 }
1736 if (pos < 15000 || (pos + 10000) > duration) {
1737 // if we're near the start or end, clear the bookmark
1738 pos = 0;
1739 }
1740
1741 // write 'pos' to the bookmark field
1742 ContentValues values = new ContentValues();
1743 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1744 Uri uri = ContentUris.withAppendedId(
1745 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1746 getContentResolver().update(uri, values, null, null);
1747 }
1748 } catch (SQLiteException ex) {
1749 }
1750 }
1751
1752 // Make sure there are at least 5 items after the currently playing item
1753 // and no more than 10 items before.
1754 private void doAutoShuffleUpdate() {
1755 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001756
The Android Open Source Project792a2202009-03-03 19:32:30 -08001757 // remove old entries
1758 if (mPlayPos > 10) {
1759 removeTracks(0, mPlayPos - 9);
1760 notify = true;
1761 }
1762 // add new entries if needed
1763 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1764 for (int i = 0; i < to_add; i++) {
1765 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001766
1767 int lookback = mHistory.size();
1768 int idx = -1;
1769 while(true) {
1770 idx = mRand.nextInt(mAutoShuffleList.length);
1771 if (!wasRecentlyUsed(idx, lookback)) {
1772 break;
1773 }
1774 lookback /= 2;
1775 }
1776 mHistory.add(idx);
1777 if (mHistory.size() > MAX_HISTORY_SIZE) {
1778 mHistory.remove(0);
1779 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001780 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001781 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001782 notify = true;
1783 }
1784 if (notify) {
1785 notifyChange(QUEUE_CHANGED);
1786 }
1787 }
1788
Marco Nelissen3f502de2010-09-28 15:07:29 -07001789 // check that the specified idx is not in the history (but only look at at
1790 // most lookbacksize entries in the history)
1791 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1792
1793 // early exit to prevent infinite loops in case idx == mPlayPos
1794 if (lookbacksize == 0) {
1795 return false;
1796 }
1797
1798 int histsize = mHistory.size();
1799 if (histsize < lookbacksize) {
1800 Log.d(LOGTAG, "lookback too big");
1801 lookbacksize = histsize;
1802 }
1803 int maxidx = histsize - 1;
1804 for (int i = 0; i < lookbacksize; i++) {
1805 long entry = mHistory.get(maxidx - i);
1806 if (entry == idx) {
1807 return true;
1808 }
1809 }
1810 return false;
1811 }
1812
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301813 private class SetBrowsedPlayerMonitor implements
1814 RemoteControlClient.OnSetBrowsedPlayerListener{
1815 @Override
1816 public void onSetBrowsedPlayer() {
1817 Log.d(LOGTAG, "onSetBrowsedPlayer");
1818 mAvrcpHandler.obtainMessage(SET_BROWSED_PLAYER).sendToTarget();
1819 }
1820 };
1821
1822 private class SetPlayItemMonitor implements
1823 RemoteControlClient.OnSetPlayItemListener{
1824 @Override
1825 public void onSetPlayItem(int scope, long uid) {
1826 Log.d(LOGTAG, "onSetPlayItem");
1827 mAvrcpHandler.obtainMessage(SET_PLAY_ITEM, scope, 0, new Long(uid)).sendToTarget();
1828 }
1829 };
1830
1831 private class GetNowPlayingEntriesMonitor implements
1832 RemoteControlClient.OnGetNowPlayingEntriesListener{
1833 @Override
1834 public void onGetNowPlayingEntries() {
1835 Log.d(LOGTAG, "onGetNowPlayingEntries");
1836 mAvrcpHandler.obtainMessage(GET_NOW_PLAYING_ENTRIES).sendToTarget();
1837 }
1838 };
1839
The Android Open Source Project792a2202009-03-03 19:32:30 -08001840 // A simple variation of Random that makes sure that the
1841 // value it returns is not equal to the value it returned
1842 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001843 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001844 private int mPrevious;
1845 private Random mRandom = new Random();
1846 public int nextInt(int interval) {
1847 int ret;
1848 do {
1849 ret = mRandom.nextInt(interval);
1850 } while (ret == mPrevious && interval > 1);
1851 mPrevious = ret;
1852 return ret;
1853 }
1854 };
1855
1856 private boolean makeAutoShuffleList() {
1857 ContentResolver res = getContentResolver();
1858 Cursor c = null;
1859 try {
1860 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1861 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1862 null, null);
1863 if (c == null || c.getCount() == 0) {
1864 return false;
1865 }
1866 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001867 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001868 for (int i = 0; i < len; i++) {
1869 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001870 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001871 }
1872 mAutoShuffleList = list;
1873 return true;
1874 } catch (RuntimeException ex) {
1875 } finally {
1876 if (c != null) {
1877 c.close();
1878 }
1879 }
1880 return false;
1881 }
1882
1883 /**
1884 * Removes the range of tracks specified from the play list. If a file within the range is
1885 * the file currently being played, playback will move to the next file after the
1886 * range.
1887 * @param first The first file to be removed
1888 * @param last The last file to be removed
1889 * @return the number of tracks deleted
1890 */
1891 public int removeTracks(int first, int last) {
1892 int numremoved = removeTracksInternal(first, last);
1893 if (numremoved > 0) {
1894 notifyChange(QUEUE_CHANGED);
1895 }
1896 return numremoved;
1897 }
1898
1899 private int removeTracksInternal(int first, int last) {
1900 synchronized (this) {
1901 if (last < first) return 0;
1902 if (first < 0) first = 0;
1903 if (last >= mPlayListLen) last = mPlayListLen - 1;
1904
1905 boolean gotonext = false;
1906 if (first <= mPlayPos && mPlayPos <= last) {
1907 mPlayPos = first;
1908 gotonext = true;
1909 } else if (mPlayPos > last) {
1910 mPlayPos -= (last - first + 1);
1911 }
1912 int num = mPlayListLen - last - 1;
1913 for (int i = 0; i < num; i++) {
1914 mPlayList[first + i] = mPlayList[last + 1 + i];
1915 }
1916 mPlayListLen -= last - first + 1;
1917
1918 if (gotonext) {
1919 if (mPlayListLen == 0) {
1920 stop(true);
1921 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001922 if (mCursor != null) {
1923 mCursor.close();
1924 mCursor = null;
1925 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001926 } else {
1927 if (mPlayPos >= mPlayListLen) {
1928 mPlayPos = 0;
1929 }
1930 boolean wasPlaying = isPlaying();
1931 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001932 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001933 if (wasPlaying) {
1934 play();
1935 }
1936 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001937 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001938 }
1939 return last - first + 1;
1940 }
1941 }
AnubhavGupta5be81b82014-12-15 20:05:23 +05301942
The Android Open Source Project792a2202009-03-03 19:32:30 -08001943 /**
1944 * Removes all instances of the track with the given id
1945 * from the playlist.
1946 * @param id The id to be removed
1947 * @return how many instances of the track were removed
1948 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001949 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001950 int numremoved = 0;
1951 synchronized (this) {
1952 for (int i = 0; i < mPlayListLen; i++) {
1953 if (mPlayList[i] == id) {
1954 numremoved += removeTracksInternal(i, i);
1955 i--;
1956 }
1957 }
1958 }
1959 if (numremoved > 0) {
1960 notifyChange(QUEUE_CHANGED);
1961 }
1962 return numremoved;
1963 }
AnubhavGupta5be81b82014-12-15 20:05:23 +05301964
The Android Open Source Project792a2202009-03-03 19:32:30 -08001965 public void setShuffleMode(int shufflemode) {
1966 synchronized(this) {
1967 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
AnubhavGupta5be81b82014-12-15 20:05:23 +05301968 /**
1969 * Some carkits send Shuffle Values same as our current
1970 * values. In such cases we need to respond back to ck
1971 */
1972 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1973 mAttributePairs, SET_ATTRIBUTE_VALUES);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001974 return;
1975 }
1976 mShuffleMode = shufflemode;
1977 if (mShuffleMode == SHUFFLE_AUTO) {
1978 if (makeAutoShuffleList()) {
1979 mPlayListLen = 0;
1980 doAutoShuffleUpdate();
1981 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001982 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001983 play();
1984 notifyChange(META_CHANGED);
AnubhavGupta5be81b82014-12-15 20:05:23 +05301985 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1986 mAttributePairs, SET_ATTRIBUTE_VALUES);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001987 return;
1988 } else {
1989 // failed to build a list of files to shuffle
1990 mShuffleMode = SHUFFLE_NONE;
1991 }
1992 }
AnubhavGupta5be81b82014-12-15 20:05:23 +05301993 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1994 mAttributePairs, SET_ATTRIBUTE_VALUES);
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05301995 notifyChange(SHUFFLE_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001996 saveQueue(false);
1997 }
1998 }
1999 public int getShuffleMode() {
2000 return mShuffleMode;
2001 }
Gaurav Asati05ed55e2014-11-11 19:11:58 +05302002
The Android Open Source Project792a2202009-03-03 19:32:30 -08002003 public void setRepeatMode(int repeatmode) {
2004 synchronized(this) {
Gaurav Asati05ed55e2014-11-11 19:11:58 +05302005 /* mPlayList not initialized */
2006 if (mPlayList == null) {
2007 Log.e(LOGTAG, "mPlayList not initialized");
2008 return;
2009 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002010 mRepeatMode = repeatmode;
Marco Nelissene41bd182012-03-14 08:24:40 -07002011 setNextTrack();
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302012 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302013 mAttributePairs, SET_ATTRIBUTE_VALUES);
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302014 notifyChange(REPEAT_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002015 saveQueue(false);
2016 }
2017 }
2018 public int getRepeatMode() {
2019 return mRepeatMode;
2020 }
2021
2022 public int getMediaMountedCount() {
2023 return mMediaMountedCount;
2024 }
2025
2026 /**
2027 * Returns the path of the currently playing file, or null if
2028 * no file is currently playing.
2029 */
2030 public String getPath() {
2031 return mFileToPlay;
2032 }
2033
2034 /**
2035 * Returns the rowid of the currently playing file, or -1 if
2036 * no file is currently playing.
2037 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07002038 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002039 synchronized (this) {
2040 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
2041 return mPlayList[mPlayPos];
2042 }
2043 }
2044 return -1;
2045 }
2046
2047 /**
2048 * Returns the position in the queue
2049 * @return the position in the queue
2050 */
2051 public int getQueuePosition() {
2052 synchronized(this) {
2053 return mPlayPos;
2054 }
2055 }
2056
2057 /**
2058 * Starts playing the track at the given position in the queue.
2059 * @param pos The position in the queue of the track that will be played.
2060 */
2061 public void setQueuePosition(int pos) {
2062 synchronized(this) {
2063 stop(false);
2064 mPlayPos = pos;
Marco Nelissene41bd182012-03-14 08:24:40 -07002065 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002066 play();
2067 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08002068 if (mShuffleMode == SHUFFLE_AUTO) {
2069 doAutoShuffleUpdate();
2070 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002071 }
2072 }
2073
2074 public String getArtistName() {
2075 synchronized(this) {
2076 if (mCursor == null) {
2077 return null;
2078 }
2079 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
2080 }
2081 }
2082
Marco Nelissenbd447b62009-06-29 14:52:05 -07002083 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002084 synchronized (this) {
2085 if (mCursor == null) {
2086 return -1;
2087 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002088 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08002089 }
2090 }
2091
2092 public String getAlbumName() {
2093 synchronized (this) {
2094 if (mCursor == null) {
2095 return null;
2096 }
2097 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
2098 }
2099 }
2100
Marco Nelissenbd447b62009-06-29 14:52:05 -07002101 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002102 synchronized (this) {
2103 if (mCursor == null) {
2104 return -1;
2105 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002106 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08002107 }
2108 }
2109
2110 public String getTrackName() {
2111 synchronized (this) {
2112 if (mCursor == null) {
2113 return null;
2114 }
2115 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
2116 }
2117 }
2118
2119 private boolean isPodcast() {
2120 synchronized (this) {
2121 if (mCursor == null) {
2122 return false;
2123 }
2124 return (mCursor.getInt(PODCASTCOLIDX) > 0);
2125 }
2126 }
2127
2128 private long getBookmark() {
2129 synchronized (this) {
2130 if (mCursor == null) {
2131 return 0;
2132 }
2133 return mCursor.getLong(BOOKMARKCOLIDX);
2134 }
2135 }
2136
2137 /**
2138 * Returns the duration of the file in milliseconds.
2139 * Currently this method returns -1 for the duration of MIDI files.
2140 */
2141 public long duration() {
2142 if (mPlayer.isInitialized()) {
2143 return mPlayer.duration();
2144 }
2145 return -1;
2146 }
2147
2148 /**
2149 * Returns the current playback position in milliseconds
2150 */
2151 public long position() {
2152 if (mPlayer.isInitialized()) {
2153 return mPlayer.position();
2154 }
2155 return -1;
2156 }
2157
2158 /**
2159 * Seeks to the position specified.
2160 *
2161 * @param pos The position to seek to, in milliseconds
2162 */
2163 public long seek(long pos) {
2164 if (mPlayer.isInitialized()) {
2165 if (pos < 0) pos = 0;
2166 if (pos > mPlayer.duration()) pos = mPlayer.duration();
2167 return mPlayer.seek(pos);
2168 }
2169 return -1;
2170 }
2171
2172 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07002173 * Sets the audio session ID.
2174 *
2175 * @param sessionId: the audio session ID.
2176 */
2177 public void setAudioSessionId(int sessionId) {
2178 synchronized (this) {
2179 mPlayer.setAudioSessionId(sessionId);
2180 }
2181 }
2182
2183 /**
2184 * Returns the audio session ID.
2185 */
2186 public int getAudioSessionId() {
2187 synchronized (this) {
2188 return mPlayer.getAudioSessionId();
2189 }
2190 }
2191
2192 /**
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302193 * Returns the player supported attribute IDs.
2194 */
2195 private void notifyAttributeIDs(String what) {
2196 Intent i = new Intent(what);
2197 i.putExtra(EXTRA_GET_RESPONSE, GET_ATTRIBUTE_IDS);
2198 i.putExtra(EXTRA_ATTIBUTE_ID_ARRAY, supportedAttributes);
2199 Log.e(LOGTAG, "notifying attributes");
2200 sendBroadcast(i);
2201 }
2202
2203 /**
2204 * Returns the player supported value IDs for given attrib.
2205 */
2206 private void notifyValueIDs(String what, byte attribute) {
2207 Intent intent = new Intent(what);
2208 intent.putExtra(EXTRA_GET_RESPONSE, GET_VALUE_IDS);
2209 intent.putExtra(EXTRA_ATTRIBUTE_ID, attribute);
2210 switch (attribute) {
2211 case ATTRIBUTE_REPEATMODE:
2212 intent.putExtra(EXTRA_VALUE_ID_ARRAY, supportedRepeatValues);
2213 break;
2214 case ATTRIBUTE_SHUFFLEMODE:
2215 intent.putExtra(EXTRA_VALUE_ID_ARRAY, supportedShuffleValues);
2216 break;
2217 default:
2218 Log.e(LOGTAG,"unsupported attribute"+attribute);
2219 intent.putExtra(EXTRA_VALUE_ID_ARRAY, unsupportedList);
2220 break;
2221 }
2222 sendBroadcast(intent);
2223 }
2224
2225 /**
2226 * Returns the player supported attrib text for given IDs.
2227 */
2228 private void notifyAttributesText(String what, byte [] attrIds) {
2229 String [] AttribStrings = new String [attrIds.length];
2230 Intent intent = new Intent(what);
2231 intent.putExtra(EXTRA_GET_RESPONSE, GET_ATTRIBUTE_TEXT);
2232 for (int i = 0; i < attrIds.length; i++) {
2233 if (attrIds[i] >= AttrStr.length) {
2234 Log.e(LOGTAG, "attrib id is"+attrIds[i]+"which is not supported");
2235 AttribStrings[i] = "";
2236 } else {
2237 AttribStrings[i] = AttrStr[attrIds[i]];
2238 }
2239 }
2240 intent.putExtra(EXTRA_ATTRIBUTE_STRING_ARRAY, AttribStrings);
2241 sendBroadcast(intent);
2242 }
2243
2244 /**
2245 * Returns the player supported value text for given IDs.
2246 */
2247 private void notifyAttributeValuesText(String what, int attribute,
2248 byte [] valIds) {
2249 Intent intent = new Intent(what);
2250 String [] ValueStrings = new String [valIds.length];
2251 intent.putExtra(EXTRA_GET_RESPONSE,GET_VALUE_TEXT);
2252 intent.putExtra(EXTRA_ATTRIBUTE_ID, attribute);
2253 Log.e(LOGTAG, "attrib is "+ attribute);
2254 String [] valueStrs = null;
2255 switch (attribute) {
2256 case ATTRIBUTE_REPEATMODE:
2257 valueStrs = new String[] {
2258 "",
2259 getString(R.string.repeat_off_notif),
2260 getString(R.string.repeat_current_notif),
2261 getString(R.string.repeat_all_notif),
2262 };
2263 break;
2264 case ATTRIBUTE_SHUFFLEMODE:
2265 valueStrs = new String[] {
2266 "",
2267 getString(R.string.shuffle_off_notif),
2268 getString(R.string.shuffle_on_notif),
2269 };
2270 break;
2271 }
2272 for (int i = 0; i < valIds.length; i++) {
2273 if ((valueStrs == null) ||
2274 (valIds[i] >= valueStrs.length)) {
2275 Log.e(LOGTAG, "value id is" + valIds[i] + "which is not supported");
2276 ValueStrings[i] = "";
2277 } else {
2278 ValueStrings[i] = valueStrs[valIds[i]];
2279 }
2280 }
2281 intent.putExtra(EXTRA_VALUE_STRING_ARRAY, ValueStrings);
2282 sendBroadcast(intent);
2283 }
2284
2285 /**
2286 * Returns the player current values for given attrib IDs.
2287 */
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302288 private void notifyAttributeValues(String what, HashMap<Byte, Boolean> attrIds, int extra) {
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302289 Intent intent = new Intent(what);
2290 intent.putExtra(EXTRA_GET_RESPONSE, extra);
2291 int j = 0;
rakesh reddyf032abb2014-09-24 15:47:45 +05302292 byte [] retValarray = new byte [attrIds.size()*2];
2293 for (int i = 0; i < attrIds.size()*2; i++) {
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302294 retValarray[i] = 0x0;
2295 }
2296
rakesh reddyf032abb2014-09-24 15:47:45 +05302297 for (Byte attribute : attrIds.keySet()) {
2298 if(attrIds.get(attribute)) {
2299 retValarray[j] = attribute;
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302300 if (attribute == ATTRIBUTE_REPEATMODE) {
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302301 retValarray[j+1] = getMappingRepeatVal(mRepeatMode);
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302302 } else if (attribute == ATTRIBUTE_SHUFFLEMODE) {
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302303 retValarray[j+1] = getMappingShuffleVal(mShuffleMode);
rakesh reddyf032abb2014-09-24 15:47:45 +05302304 }
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302305 j += 2;
rakesh reddyf032abb2014-09-24 15:47:45 +05302306 } else {
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302307 retValarray[j] = attribute;
2308 retValarray[j+1] = ERROR_NOTSUPPORTED;
2309 j += 2;
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302310 }
2311 }
2312 intent.putExtra(EXTRA_ATTRIB_VALUE_PAIRS, retValarray);
2313 sendBroadcast(intent);
2314 }
2315
2316 /**
2317 * Sets the values to current player for given attrib IDs.
2318 */
2319 private void setValidAttributes(byte [] attribValuePairs) {
2320 byte attrib, value;
2321
2322 for (int i = 0; i < (attribValuePairs.length-1); i += 2) {
2323 attrib = attribValuePairs[i];
2324 value = attribValuePairs[i+1];
2325 switch(attrib) {
2326 case ATTRIBUTE_REPEATMODE:
2327 if (isValidRepeatMode(value)) {
2328 setRepeatMode(getMappingRepeatMode(value));
2329 }
2330 break;
2331 case ATTRIBUTE_SHUFFLEMODE:
2332 if (isValidShuffleMode(value)) {
2333 setShuffleMode(getMappingShuffleMode(value));
2334 }
2335 break;
2336 default:
2337 Log.e(LOGTAG,"Unknown attribute"+attrib);
rakesh reddyf032abb2014-09-24 15:47:45 +05302338 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
Ayan Ghoshc0a67ae2014-11-27 15:28:59 +05302339 mAttributePairs, SET_ATTRIBUTE_VALUES);
Ayan Ghosh6ceaa692014-07-23 21:03:40 +05302340 break;
2341 }
2342 }
2343 }
2344
2345 byte getMappingRepeatVal (int repeatMode) {
2346 switch (repeatMode) {
2347 case REPEAT_NONE:
2348 return VALUE_REPEATMODE_OFF;
2349 case REPEAT_CURRENT:
2350 return VALUE_REPEATMODE_SINGLE;
2351 case REPEAT_ALL:
2352 return VALUE_REPEATMODE_ALL;
2353 default:
2354 return VALUE_REPEATMODE_OFF;
2355 }
2356 }
2357
2358 byte getMappingShuffleVal (int shuffleMode) {
2359 switch (shuffleMode) {
2360 case SHUFFLE_NONE:
2361 return VALUE_SHUFFLEMODE_OFF;
2362 case SHUFFLE_NORMAL:
2363 return VALUE_SHUFFLEMODE_ALL;
2364 case SHUFFLE_AUTO:
2365 return VALUE_SHUFFLEMODE_ALL;
2366 default:
2367 return VALUE_SHUFFLEMODE_OFF;
2368 }
2369 }
2370
2371 int getMappingRepeatMode (byte repeatVal) {
2372 switch (repeatVal) {
2373 case VALUE_REPEATMODE_OFF:
2374 return REPEAT_NONE;
2375 case VALUE_REPEATMODE_SINGLE:
2376 return REPEAT_CURRENT;
2377 case VALUE_REPEATMODE_ALL:
2378 case VALUE_REPEATMODE_GROUP:
2379 return REPEAT_ALL;
2380 default:
2381 return REPEAT_NONE;
2382 }
2383 }
2384
2385 int getMappingShuffleMode (byte shuffleVal) {
2386 switch (shuffleVal) {
2387 case VALUE_SHUFFLEMODE_OFF:
2388 return SHUFFLE_NONE;
2389 case VALUE_SHUFFLEMODE_ALL:
2390 case VALUE_SHUFFLEMODE_GROUP:
2391 return SHUFFLE_NORMAL;
2392 default:
2393 return SHUFFLE_NONE;
2394 }
2395 }
2396
2397 /**
2398 * Validates the value with CMDSET for Repeat mode.
2399 */
2400 private boolean isValidRepeatMode(byte value) {
2401 if (value == 0) {
2402 return false;
2403 }
2404 value--;
2405 if ((value >= REPEAT_NONE) && ( value <= REPEAT_ALL)) {
2406 return true;
2407 }
2408 return false;
2409 }
2410
2411 /**
2412 * Validates the value with CMDSET for Shuffle mode.
2413 */
2414 private boolean isValidShuffleMode(byte value) {
2415 if (value == 0) {
2416 return false;
2417 }
2418 value--;
2419 // check the mapping for local suffle and argument
2420 if ((value >= SHUFFLE_NONE) && ( value <= SHUFFLE_AUTO)) {
2421 return true;
2422 }
2423 return false;
2424 }
2425
2426 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08002427 * Provides a unified interface for dealing with midi files and
2428 * other media files.
2429 */
2430 private class MultiPlayer {
Marco Nelissen30867022012-03-14 16:04:34 -07002431 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
2432 private CompatMediaPlayer mNextMediaPlayer;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002433 private Handler mHandler;
2434 private boolean mIsInitialized = false;
2435
2436 public MultiPlayer() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002437 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002438 }
2439
The Android Open Source Project792a2202009-03-03 19:32:30 -08002440 public void setDataSource(String path) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07002441 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
2442 if (mIsInitialized) {
2443 setNextDataSource(null);
2444 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002445 }
2446
Marco Nelissen90d1f622012-04-05 12:27:56 -07002447 private boolean setDataSourceImpl(MediaPlayer player, String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002448 try {
Marco Nelissene41bd182012-03-14 08:24:40 -07002449 player.reset();
2450 player.setOnPreparedListener(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002451 if (path.startsWith("content://")) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002452 player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
The Android Open Source Project792a2202009-03-03 19:32:30 -08002453 } else {
Marco Nelissene41bd182012-03-14 08:24:40 -07002454 player.setDataSource(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002455 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002456 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
2457 player.prepare();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002458 } catch (IOException ex) {
2459 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07002460 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002461 } catch (IllegalArgumentException ex) {
2462 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07002463 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002464 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002465 player.setOnCompletionListener(listener);
2466 player.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07002467 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
2468 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2469 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
2470 sendBroadcast(i);
Marco Nelissen90d1f622012-04-05 12:27:56 -07002471 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002472 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002473
2474 public void setNextDataSource(String path) {
2475 mCurrentMediaPlayer.setNextMediaPlayer(null);
2476 if (mNextMediaPlayer != null) {
2477 mNextMediaPlayer.release();
2478 mNextMediaPlayer = null;
2479 }
2480 if (path == null) {
2481 return;
2482 }
Marco Nelissen30867022012-03-14 16:04:34 -07002483 mNextMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07002484 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
2485 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
Marco Nelissen90d1f622012-04-05 12:27:56 -07002486 if (setDataSourceImpl(mNextMediaPlayer, path)) {
2487 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
2488 } else {
2489 // failed to open next, we'll transition the old fashioned way,
2490 // which will skip over the faulty file
2491 mNextMediaPlayer.release();
2492 mNextMediaPlayer = null;
2493 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002494 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002495
2496 public boolean isInitialized() {
2497 return mIsInitialized;
2498 }
2499
2500 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08002501 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
Marco Nelissene41bd182012-03-14 08:24:40 -07002502 mCurrentMediaPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002503 }
2504
2505 public void stop() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002506 mCurrentMediaPlayer.reset();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002507 mIsInitialized = false;
2508 }
2509
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002510 /**
2511 * You CANNOT use this player anymore after calling release()
2512 */
2513 public void release() {
2514 stop();
Marco Nelissene41bd182012-03-14 08:24:40 -07002515 mCurrentMediaPlayer.release();
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002516 }
2517
The Android Open Source Project792a2202009-03-03 19:32:30 -08002518 public void pause() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002519 mCurrentMediaPlayer.pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002520 }
2521
The Android Open Source Project792a2202009-03-03 19:32:30 -08002522 public void setHandler(Handler handler) {
2523 mHandler = handler;
2524 }
2525
2526 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
2527 public void onCompletion(MediaPlayer mp) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002528 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
2529 mCurrentMediaPlayer.release();
2530 mCurrentMediaPlayer = mNextMediaPlayer;
2531 mNextMediaPlayer = null;
2532 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
2533 } else {
2534 // Acquire a temporary wakelock, since when we return from
2535 // this callback the MediaPlayer will release its wakelock
2536 // and allow the device to go to sleep.
2537 // This temporary wakelock is released when the RELEASE_WAKELOCK
2538 // message is processed, but just in case, put a timeout on it.
2539 mWakeLock.acquire(30000);
2540 mHandler.sendEmptyMessage(TRACK_ENDED);
2541 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
2542 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002543 }
2544 };
2545
The Android Open Source Project792a2202009-03-03 19:32:30 -08002546 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
2547 public boolean onError(MediaPlayer mp, int what, int extra) {
2548 switch (what) {
2549 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
2550 mIsInitialized = false;
Marco Nelissene41bd182012-03-14 08:24:40 -07002551 mCurrentMediaPlayer.release();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002552 // Creating a new MediaPlayer and settings its wakemode does not
2553 // require the media service, so it's OK to do this now, while the
2554 // service is still being restarted
Marco Nelissen30867022012-03-14 16:04:34 -07002555 mCurrentMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07002556 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002557 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
2558 return true;
2559 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08002560 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002561 break;
2562 }
2563 return false;
2564 }
2565 };
2566
2567 public long duration() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002568 return mCurrentMediaPlayer.getDuration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002569 }
2570
2571 public long position() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002572 return mCurrentMediaPlayer.getCurrentPosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002573 }
2574
2575 public long seek(long whereto) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002576 mCurrentMediaPlayer.seekTo((int) whereto);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002577 return whereto;
2578 }
2579
2580 public void setVolume(float vol) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002581 mCurrentMediaPlayer.setVolume(vol, vol);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002582 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002583
2584 public void setAudioSessionId(int sessionId) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002585 mCurrentMediaPlayer.setAudioSessionId(sessionId);
Eric Laurent1cc72a12010-06-28 11:27:01 -07002586 }
2587
2588 public int getAudioSessionId() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002589 return mCurrentMediaPlayer.getAudioSessionId();
Eric Laurent1cc72a12010-06-28 11:27:01 -07002590 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002591 }
2592
Marco Nelissen30867022012-03-14 16:04:34 -07002593 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2594
2595 private boolean mCompatMode = true;
2596 private MediaPlayer mNextPlayer;
2597 private OnCompletionListener mCompletion;
2598
2599 public CompatMediaPlayer() {
2600 try {
2601 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2602 mCompatMode = false;
2603 } catch (NoSuchMethodException e) {
2604 mCompatMode = true;
2605 super.setOnCompletionListener(this);
2606 }
2607 }
2608
2609 public void setNextMediaPlayer(MediaPlayer next) {
2610 if (mCompatMode) {
2611 mNextPlayer = next;
2612 } else {
2613 super.setNextMediaPlayer(next);
2614 }
2615 }
2616
2617 @Override
2618 public void setOnCompletionListener(OnCompletionListener listener) {
2619 if (mCompatMode) {
2620 mCompletion = listener;
2621 } else {
2622 super.setOnCompletionListener(listener);
2623 }
2624 }
2625
2626 @Override
2627 public void onCompletion(MediaPlayer mp) {
2628 if (mNextPlayer != null) {
2629 // as it turns out, starting a new MediaPlayer on the completion
2630 // of a previous player ends up slightly overlapping the two
2631 // playbacks, so slightly delaying the start of the next player
2632 // gives a better user experience
2633 SystemClock.sleep(50);
2634 mNextPlayer.start();
2635 }
2636 mCompletion.onCompletion(this);
2637 }
2638 }
2639
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002640 /*
2641 * By making this a static class with a WeakReference to the Service, we
2642 * ensure that the Service can be GCd even when the system process still
2643 * has a remote reference to the stub.
2644 */
2645 static class ServiceStub extends IMediaPlaybackService.Stub {
2646 WeakReference<MediaPlaybackService> mService;
2647
2648 ServiceStub(MediaPlaybackService service) {
2649 mService = new WeakReference<MediaPlaybackService>(service);
2650 }
2651
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002652 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08002653 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002654 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002655 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002656 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002657 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002658 }
2659 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002660 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002661 }
2662 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002663 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002664 }
2665 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002666 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002667 }
2668 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002669 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002670 }
2671 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002672 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002673 }
2674 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002675 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002676 }
2677 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002678 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002679 }
2680 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08002681 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002682 }
2683 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002684 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002685 }
2686 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002687 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002688 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002689 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002690 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002691 }
2692 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002693 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002694 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002695 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002696 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002697 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002698 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002699 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002700 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002701 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002702 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002703 }
2704 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002705 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002706 }
2707 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002708 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002709 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002710 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002711 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002712 }
2713 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002714 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002715 }
2716 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002717 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002718 }
2719 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002720 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002721 }
2722 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002723 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002724 }
2725 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002726 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002727 }
2728 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002729 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002730 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002731 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002732 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002733 }
2734 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002735 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002736 }
2737 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002738 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002739 }
2740 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002741 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002742 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002743 public int getAudioSessionId() {
2744 return mService.get().getAudioSessionId();
2745 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002746 }
Marco Nelissen39888902010-03-02 10:27:05 -08002747
2748 @Override
2749 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002750 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002751 writer.println("Currently loaded:");
2752 writer.println(getArtistName());
2753 writer.println(getAlbumName());
2754 writer.println(getTrackName());
2755 writer.println(getPath());
2756 writer.println("playing: " + mIsSupposedToBePlaying);
Marco Nelissene41bd182012-03-14 08:24:40 -07002757 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002758 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002759 MusicUtils.debugDump(writer);
2760 }
2761
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002762 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002763}