FileDocCategorySizeDatePackage
MediaPlaybackService.javaAPI DocAndroid 1.5 API67200Wed May 06 22:42:46 BST 2009com.android.music

MediaPlaybackService

public class MediaPlaybackService extends android.app.Service
Provides "background" audio playback capabilities, allowing the user to switch between activities without stopping playback.

Fields Summary
public static final int
NOW
used to specify whether enqueue() should start playing the new list of files right away, next or once all the currently queued files have been played
public static final int
NEXT
public static final int
LAST
public static final int
PLAYBACKSERVICE_STATUS
public static final int
SHUFFLE_NONE
public static final int
SHUFFLE_NORMAL
public static final int
SHUFFLE_AUTO
public static final int
REPEAT_NONE
public static final int
REPEAT_CURRENT
public static final int
REPEAT_ALL
public static final String
PLAYSTATE_CHANGED
public static final String
META_CHANGED
public static final String
QUEUE_CHANGED
public static final String
PLAYBACK_COMPLETE
public static final String
ASYNC_OPEN_COMPLETE
public static final String
SERVICECMD
public static final String
CMDNAME
public static final String
CMDTOGGLEPAUSE
public static final String
CMDSTOP
public static final String
CMDPAUSE
public static final String
CMDPREVIOUS
public static final String
CMDNEXT
public static final String
TOGGLEPAUSE_ACTION
public static final String
PAUSE_ACTION
public static final String
PREVIOUS_ACTION
public static final String
NEXT_ACTION
private static final int
PHONE_CHANGED
private static final int
TRACK_ENDED
private static final int
RELEASE_WAKELOCK
private static final int
SERVER_DIED
private static final int
FADEIN
private static final int
MAX_HISTORY_SIZE
private MultiPlayer
mPlayer
private String
mFileToPlay
private com.android.internal.telephony.PhoneStateIntentReceiver
mPsir
private int
mShuffleMode
private int
mRepeatMode
private int
mMediaMountedCount
private int[]
mAutoShuffleList
private boolean
mOneShot
private int[]
mPlayList
private int
mPlayListLen
private Vector
mHistory
private android.database.Cursor
mCursor
private int
mPlayPos
private static final String
LOGTAG
private final Shuffler
mRand
private int
mOpenFailedCounter
String[]
mCursorCols
private static final int
IDCOLIDX
private static final int
PODCASTCOLIDX
private static final int
BOOKMARKCOLIDX
private android.content.BroadcastReceiver
mUnmountReceiver
private android.os.PowerManager.WakeLock
mWakeLock
private int
mServiceStartId
private boolean
mServiceInUse
private boolean
mResumeAfterCall
private boolean
mWasPlaying
private boolean
mQuietMode
private android.content.SharedPreferences
mPreferences
private int
mCardId
private MediaAppWidgetProvider
mAppWidgetProvider
private static final int
IDLE_DELAY
private android.os.Handler
mPhoneHandler
private android.os.Handler
mMediaplayerHandler
private android.content.BroadcastReceiver
mIntentReceiver
private final char[]
hexdigits
private android.os.Handler
mDelayedStopHandler
private final IMediaPlaybackService.Stub
mBinder
Constructors Summary
public MediaPlaybackService()


      
        mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
        mPsir.notifyPhoneCallState(PHONE_CHANGED);
    
Methods Summary
private voidaddToPlayList(int[] list, int position)

        int addlen = list.length;
        if (position < 0) { // overwrite
            mPlayListLen = 0;
            position = 0;
        }
        ensurePlayListCapacity(mPlayListLen + addlen);
        if (position > mPlayListLen) {
            position = mPlayListLen;
        }
        
        // move part of list after insertion point
        int tailsize = mPlayListLen - position;
        for (int i = tailsize ; i > 0 ; i--) {
            mPlayList[position + i] = mPlayList[position + i - addlen]; 
        }
        
        // copy list into playlist
        for (int i = 0; i < addlen; i++) {
            mPlayList[position + i] = list[i];
        }
        mPlayListLen += addlen;
    
public voidcloseExternalStorageFiles(java.lang.String storagePath)
Called when we receive a ACTION_MEDIA_EJECT notification.

param
storagePath path to mount point for the removed media

    
                          
        
        // stop playback and clean up if the SD card is going to be unmounted.
        stop(true);
        notifyChange(QUEUE_CHANGED);
        notifyChange(META_CHANGED);
    
private voiddoAutoShuffleUpdate()

        boolean notify = false;
        // remove old entries
        if (mPlayPos > 10) {
            removeTracks(0, mPlayPos - 9);
            notify = true;
        }
        // add new entries if needed
        int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
        for (int i = 0; i < to_add; i++) {
            // pick something at random from the list
            int idx = mRand.nextInt(mAutoShuffleList.length);
            Integer which = mAutoShuffleList[idx];
            ensurePlayListCapacity(mPlayListLen + 1);
            mPlayList[mPlayListLen++] = which;
            notify = true;
        }
        if (notify) {
            notifyChange(QUEUE_CHANGED);
        }
    
public longduration()
Returns the duration of the file in milliseconds. Currently this method returns -1 for the duration of MIDI files.

        if (mPlayer.isInitialized()) {
            return mPlayer.duration();
        }
        return -1;
    
public voidenqueue(int[] list, int action)
Appends a list of tracks to the current playlist. If nothing is playing currently, playback will be started at the first track. If the action is NOW, playback will switch to the first of the new tracks immediately.

param
list The list of tracks to append.
param
action NOW, NEXT or LAST

        synchronized(this) {
            if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
                addToPlayList(list, mPlayPos + 1);
                notifyChange(QUEUE_CHANGED);
            } else {
                // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
                addToPlayList(list, Integer.MAX_VALUE);
                notifyChange(QUEUE_CHANGED);
                if (action == NOW) {
                    mPlayPos = mPlayListLen - list.length;
                    openCurrent();
                    play();
                    notifyChange(META_CHANGED);
                    return;
                }
            }
            if (mPlayPos < 0) {
                mPlayPos = 0;
                openCurrent();
                play();
                notifyChange(META_CHANGED);
            }
        }
    
private voidensurePlayListCapacity(int size)

        if (mPlayList == null || size > mPlayList.length) {
            // reallocate at 2x requested size so we don't
            // need to grow and copy the array for every
            // insert
            int [] newlist = new int[size * 2];
            int len = mPlayListLen;
            for (int i = 0; i < len; i++) {
                newlist[i] = mPlayList[i];
            }
            mPlayList = newlist;
        }
        // FIXME: shrink the array when the needed size is much smaller
        // than the allocated size
    
public intgetAlbumId()

        synchronized (this) {
            if (mCursor == null) {
                return -1;
            }
            return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
        }
    
public java.lang.StringgetAlbumName()

        synchronized (this) {
            if (mCursor == null) {
                return null;
            }
            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
        }
    
public intgetArtistId()

        synchronized (this) {
            if (mCursor == null) {
                return -1;
            }
            return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
        }
    
public java.lang.StringgetArtistName()

        synchronized(this) {
            if (mCursor == null) {
                return null;
            }
            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
        }
    
public intgetAudioId()
Returns the rowid of the currently playing file, or -1 if no file is currently playing.

        synchronized (this) {
            if (mPlayPos >= 0 && mPlayer.isInitialized()) {
                return mPlayList[mPlayPos];
            }
        }
        return -1;
    
private longgetBookmark()

        synchronized (this) {
            if (mCursor == null) {
                return 0;
            }
            return mCursor.getLong(BOOKMARKCOLIDX);
        }
    
public intgetMediaMountedCount()

        return mMediaMountedCount;
    
public java.lang.StringgetPath()
Returns the path of the currently playing file, or null if no file is currently playing.

        return mFileToPlay;
    
public int[]getQueue()
Returns the current play list

return
An array of integers containing the IDs of the tracks in the play list

        synchronized (this) {
            int len = mPlayListLen;
            int [] list = new int[len];
            for (int i = 0; i < len; i++) {
                list[i] = mPlayList[i];
            }
            return list;
        }
    
public intgetQueuePosition()
Returns the position in the queue

return
the position in the queue

        synchronized(this) {
            return mPlayPos;
        }
    
public intgetRepeatMode()

        return mRepeatMode;
    
public intgetShuffleMode()

        return mShuffleMode;
    
public java.lang.StringgetTrackName()

        synchronized (this) {
            if (mCursor == null) {
                return null;
            }
            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
        }
    
private voidgotoIdleState()

        NotificationManager nm =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nm.cancel(PLAYBACKSERVICE_STATUS);
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        Message msg = mDelayedStopHandler.obtainMessage();
        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    
public booleanisPlaying()
Returns whether playback is currently paused

return
true if playback is paused, false if not

        if (mPlayer.isInitialized()) {
            return mPlayer.isPlaying();
        }
        return false;
    
private booleanisPodcast()

        synchronized (this) {
            if (mCursor == null) {
                return false;
            }
            return (mCursor.getInt(PODCASTCOLIDX) > 0);
        }
    
private booleanmakeAutoShuffleList()

        ContentResolver res = getContentResolver();
        Cursor c = null;
        try {
            c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
                    null, null);
            if (c == null || c.getCount() == 0) {
                return false;
            }
            int len = c.getCount();
            int[] list = new int[len];
            for (int i = 0; i < len; i++) {
                c.moveToNext();
                list[i] = c.getInt(0);
            }
            mAutoShuffleList = list;
            return true;
        } catch (RuntimeException ex) {
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return false;
    
public voidmoveQueueItem(int index1, int index2)
Moves the item at index1 to index2.

param
index1
param
index2

        synchronized (this) {
            if (index1 >= mPlayListLen) {
                index1 = mPlayListLen - 1;
            }
            if (index2 >= mPlayListLen) {
                index2 = mPlayListLen - 1;
            }
            if (index1 < index2) {
                int tmp = mPlayList[index1];
                for (int i = index1; i < index2; i++) {
                    mPlayList[i] = mPlayList[i+1];
                }
                mPlayList[index2] = tmp;
                if (mPlayPos == index1) {
                    mPlayPos = index2;
                } else if (mPlayPos >= index1 && mPlayPos <= index2) {
                        mPlayPos--;
                }
            } else if (index2 < index1) {
                int tmp = mPlayList[index1];
                for (int i = index1; i > index2; i--) {
                    mPlayList[i] = mPlayList[i-1];
                }
                mPlayList[index2] = tmp;
                if (mPlayPos == index1) {
                    mPlayPos = index2;
                } else if (mPlayPos >= index2 && mPlayPos <= index1) {
                        mPlayPos++;
                }
            }
            notifyChange(QUEUE_CHANGED);
        }
    
public voidnext(boolean force)

        synchronized (this) {
            if (mOneShot) {
                // we were playing a specific file not part of a playlist, so there is no 'next'
                seek(0);
                play();
                return;
            }

            // Store the current file in the history, but keep the history at a
            // reasonable size
            if (mPlayPos >= 0) {
                mHistory.add(Integer.valueOf(mPlayPos));
            }
            if (mHistory.size() > MAX_HISTORY_SIZE) {
                mHistory.removeElementAt(0);
            }

            if (mShuffleMode == SHUFFLE_NORMAL) {
                // Pick random next track from the not-yet-played ones
                // TODO: make it work right after adding/removing items in the queue.

                int numTracks = mPlayListLen;
                int[] tracks = new int[numTracks];
                for (int i=0;i < numTracks; i++) {
                    tracks[i] = i;
                }

                int numHistory = mHistory.size();
                int numUnplayed = numTracks;
                for (int i=0;i < numHistory; i++) {
                    int idx = mHistory.get(i).intValue();
                    if (idx < numTracks && tracks[idx] >= 0) {
                        numUnplayed--;
                        tracks[idx] = -1;
                    }
                }

                // 'numUnplayed' now indicates how many tracks have not yet
                // been played, and 'tracks' contains the indices of those
                // tracks.
                if (numUnplayed <=0) {
                    // everything's already been played
                    if (mRepeatMode == REPEAT_ALL || force) {
                        //pick from full set
                        numUnplayed = numTracks;
                        for (int i=0;i < numTracks; i++) {
                            tracks[i] = i;
                        }
                    } else {
                        // all done
                        gotoIdleState();
                        return;
                    }
                }
                int skip = mRand.nextInt(numUnplayed);
                int cnt = -1;
                while (true) {
                    while (tracks[++cnt] < 0)
                        ;
                    skip--;
                    if (skip < 0) {
                        break;
                    }
                }
                mPlayPos = cnt;
            } else if (mShuffleMode == SHUFFLE_AUTO) {
                doAutoShuffleUpdate();
                mPlayPos++;
            } else {
                if (mPlayPos >= mPlayListLen - 1) {
                    // we're at the end of the list
                    if (mRepeatMode == REPEAT_NONE && !force) {
                        // all done
                        gotoIdleState();
                        notifyChange(PLAYBACK_COMPLETE);
                        return;
                    } else if (mRepeatMode == REPEAT_ALL || force) {
                        mPlayPos = 0;
                    }
                } else {
                    mPlayPos++;
                }
            }
            saveBookmarkIfNeeded();
            stop(false);
            openCurrent();
            play();
            notifyChange(META_CHANGED);
        }
    
private voidnotifyChange(java.lang.String what)
Notify the change-receivers that something has changed. The intent that is sent contains the following data for the currently playing track: "id" - Integer: the database row ID "artist" - String: the name of the artist "album" - String: the name of the album "track" - String: the name of the track The intent has an action that is one of "com.android.music.metachanged" "com.android.music.queuechanged", "com.android.music.playbackcomplete" "com.android.music.playstatechanged" respectively indicating that a new track has started playing, that the playback queue has changed, that playback has stopped because the last file in the list has been played, or that the play-state changed (paused/resumed).

        
        Intent i = new Intent(what);
        i.putExtra("id", Integer.valueOf(getAudioId()));
        i.putExtra("artist", getArtistName());
        i.putExtra("album",getAlbumName());
        i.putExtra("track", getTrackName());
        sendBroadcast(i);
        
        if (what.equals(QUEUE_CHANGED)) {
            saveQueue(true);
        } else {
            saveQueue(false);
        }
        
        // Share this notification directly with our widgets
        mAppWidgetProvider.notifyChange(this, what);
    
public android.os.IBinderonBind(android.content.Intent intent)

        mDelayedStopHandler.removeCallbacksAndMessages(null);
        mServiceInUse = true;
        return mBinder;
    
public voidonCreate()

        super.onCreate();
        
        mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
        mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
        
        registerExternalStorageListener();

        // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
        mPlayer = new MultiPlayer();
        mPlayer.setHandler(mMediaplayerHandler);

        // Clear leftover notification in case this service previously got killed while playing
        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nm.cancel(PLAYBACKSERVICE_STATUS);
        
        reloadQueue();
        
        IntentFilter commandFilter = new IntentFilter();
        commandFilter.addAction(SERVICECMD);
        commandFilter.addAction(TOGGLEPAUSE_ACTION);
        commandFilter.addAction(PAUSE_ACTION);
        commandFilter.addAction(NEXT_ACTION);
        commandFilter.addAction(PREVIOUS_ACTION);
        registerReceiver(mIntentReceiver, commandFilter);
        
        mPsir.registerIntent();
        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
        mWakeLock.setReferenceCounted(false);

        // If the service was idle, but got killed before it stopped itself, the
        // system will relaunch it. Make sure it gets stopped again in that case.
        Message msg = mDelayedStopHandler.obtainMessage();
        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    
public voidonDestroy()

        // Check that we're not being destroyed while something is still playing.
        if (isPlaying()) {
            Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
        }
        // and for good measure, call mPlayer.stop(), which calls MediaPlayer.reset(), which
        // releases the MediaPlayer's wake lock, if any.
        mPlayer.stop();
        
        if (mCursor != null) {
            mCursor.close();
            mCursor = null;
        }

        unregisterReceiver(mIntentReceiver);
        if (mUnmountReceiver != null) {
            unregisterReceiver(mUnmountReceiver);
            mUnmountReceiver = null;
        }
        mPsir.unregisterIntent();
        mWakeLock.release();
        super.onDestroy();
    
public voidonRebind(android.content.Intent intent)

        mDelayedStopHandler.removeCallbacksAndMessages(null);
        mServiceInUse = true;
    
public voidonStart(android.content.Intent intent, int startId)

        mServiceStartId = startId;
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        
        String action = intent.getAction();
        String cmd = intent.getStringExtra("command");
        
        if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
            next(true);
        } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
            prev();
        } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
            if (isPlaying()) {
                pause();
            } else {
                play();
            }
        } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
            pause();
        } else if (CMDSTOP.equals(cmd)) {
            pause();
            seek(0);
        }
        
        // make sure the service will shut down on its own if it was
        // just started but not bound to and nothing is playing
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        Message msg = mDelayedStopHandler.obtainMessage();
        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    
public booleanonUnbind(android.content.Intent intent)

        mServiceInUse = false;

        // Take a snapshot of the current playlist
        saveQueue(true);

        if (isPlaying() || mResumeAfterCall) {
            // something is currently playing, or will be playing once 
            // an in-progress call ends, so don't stop the service now.
            return true;
        }
        
        // If there is a playlist but playback is paused, then wait a while
        // before stopping the service, so that pause/resume isn't slow.
        // Also delay stopping the service if we're transitioning between tracks.
        if (mPlayListLen > 0  || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
            Message msg = mDelayedStopHandler.obtainMessage();
            mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
            return true;
        }
        
        // No active playlist, OK to stop the service right now
        stopSelf(mServiceStartId);
        return true;
    
public voidopen(int[] list, int position)
Replaces the current playlist with a new list, and prepares for starting playback at the specified position in the list, or a random position if the specified position is 0.

param
list The new list of tracks.

        synchronized (this) {
            if (mShuffleMode == SHUFFLE_AUTO) {
                mShuffleMode = SHUFFLE_NORMAL;
            }
            int oldId = getAudioId();
            int listlength = list.length;
            boolean newlist = true;
            if (mPlayListLen == listlength) {
                // possible fast path: list might be the same
                newlist = false;
                for (int i = 0; i < listlength; i++) {
                    if (list[i] != mPlayList[i]) {
                        newlist = true;
                        break;
                    }
                }
            }
            if (newlist) {
                addToPlayList(list, -1);
                notifyChange(QUEUE_CHANGED);
            }
            int oldpos = mPlayPos;
            if (position >= 0) {
                mPlayPos = position;
            } else {
                mPlayPos = mRand.nextInt(mPlayListLen);
            }
            mHistory.clear();

            saveBookmarkIfNeeded();
            openCurrent();
            if (oldId != getAudioId()) {
                notifyChange(META_CHANGED);
            }
        }
    
public voidopen(java.lang.String path, boolean oneshot)
Opens the specified file and readies it for playback.

param
path The full path of the file to be opened.
param
oneshot when set to true, playback will stop after this file completes, instead of moving on to the next track in the list

        synchronized (this) {
            if (path == null) {
                return;
            }
            
            if (oneshot) {
                mRepeatMode = REPEAT_NONE;
                ensurePlayListCapacity(1);
                mPlayListLen = 1;
                mPlayPos = -1;
            }
            
            // if mCursor is null, try to associate path with a database cursor
            if (mCursor == null) {

                ContentResolver resolver = getContentResolver();
                Uri uri;
                String where;
                String selectionArgs[];
                if (path.startsWith("content://media/")) {
                    uri = Uri.parse(path);
                    where = null;
                    selectionArgs = null;
                } else {
                   uri = MediaStore.Audio.Media.getContentUriForPath(path);
                   where = MediaStore.Audio.Media.DATA + "=?";
                   selectionArgs = new String[] { path };
                }
                
                try {
                    mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
                    if  (mCursor != null) {
                        if (mCursor.getCount() == 0) {
                            mCursor.close();
                            mCursor = null;
                        } else {
                            mCursor.moveToNext();
                            ensurePlayListCapacity(1);
                            mPlayListLen = 1;
                            mPlayList[0] = mCursor.getInt(IDCOLIDX);
                            mPlayPos = 0;
                        }
                    }
                } catch (UnsupportedOperationException ex) {
                }
            }
            mFileToPlay = path;
            mPlayer.setDataSource(mFileToPlay);
            mOneShot = oneshot;
            if (! mPlayer.isInitialized()) {
                stop(true);
                if (mOpenFailedCounter++ < 10 &&  mPlayListLen > 1) {
                    // beware: this ends up being recursive because next() calls open() again.
                    next(false);
                }
                if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
                    // need to make sure we only shows this once
                    mOpenFailedCounter = 0;
                    if (!mQuietMode) {
                        Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
                    }
                }
            } else {
                mOpenFailedCounter = 0;
            }
        }
    
public voidopenAsync(java.lang.String path)

        synchronized (this) {
            if (path == null) {
                return;
            }
            
            mRepeatMode = REPEAT_NONE;
            ensurePlayListCapacity(1);
            mPlayListLen = 1;
            mPlayPos = -1;
            
            mFileToPlay = path;
            mCursor = null;
            mPlayer.setDataSourceAsync(mFileToPlay);
            mOneShot = true;
        }
    
private voidopenCurrent()

        synchronized (this) {
            if (mCursor != null) {
                mCursor.close();
                mCursor = null;
            }
            if (mPlayListLen == 0) {
                return;
            }
            stop(false);

            String id = String.valueOf(mPlayList[mPlayPos]);
            
            mCursor = getContentResolver().query(
                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    mCursorCols, "_id=" + id , null, null);
            if (mCursor != null) {
                mCursor.moveToFirst();
                open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
                // go to bookmark if needed
                if (isPodcast()) {
                    long bookmark = getBookmark();
                    // Start playing a little bit before the bookmark,
                    // so it's easier to get back in to the narrative.
                    seek(bookmark - 5000);
                }
            }
        }
    
public voidpause()
Pauses playback (call play() to resume)

        if (isPlaying()) {
            mPlayer.pause();
            gotoIdleState();
            setForeground(false);
            mWasPlaying = false;
            notifyChange(PLAYSTATE_CHANGED);
            saveBookmarkIfNeeded();
        }
    
public voidplay()
Starts playback of a previously opened file.

        if (mPlayer.isInitialized()) {
            mPlayer.start();
            setForeground(true);

            NotificationManager nm = (NotificationManager)
            getSystemService(Context.NOTIFICATION_SERVICE);
    
            RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
            views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
            if (getAudioId() < 0) {
                // streaming
                views.setTextViewText(R.id.trackname, getPath());
                views.setTextViewText(R.id.artistalbum, null);
            } else {
                String artist = getArtistName();
                views.setTextViewText(R.id.trackname, getTrackName());
                if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
                    artist = getString(R.string.unknown_artist_name);
                }
                String album = getAlbumName();
                if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
                    album = getString(R.string.unknown_album_name);
                }
                
                views.setTextViewText(R.id.artistalbum,
                        getString(R.string.notification_artist_album, artist, album)
                        );
            }
            
            Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
            statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            Notification status = new Notification();
            status.contentView = views;
            status.flags |= Notification.FLAG_ONGOING_EVENT;
            status.icon = R.drawable.stat_notify_musicplayer;
            status.contentIntent = PendingIntent.getActivity(this, 0,
                    new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
            nm.notify(PLAYBACKSERVICE_STATUS, status);
            if (!mWasPlaying) {
                notifyChange(PLAYSTATE_CHANGED);
            }
            mWasPlaying = true;
        } else if (mPlayListLen <= 0) {
            // This is mostly so that if you press 'play' on a bluetooth headset
            // without every having played anything before, it will still play
            // something.
            setShuffleMode(SHUFFLE_AUTO);
        }
    
public longposition()
Returns the current playback position in milliseconds

        if (mPlayer.isInitialized()) {
            return mPlayer.position();
        }
        return -1;
    
public voidprev()

        synchronized (this) {
            if (mOneShot) {
                // we were playing a specific file not part of a playlist, so there is no 'previous'
                seek(0);
                play();
                return;
            }
            if (mShuffleMode == SHUFFLE_NORMAL) {
                // go to previously-played track and remove it from the history
                int histsize = mHistory.size();
                if (histsize == 0) {
                    // prev is a no-op
                    return;
                }
                Integer pos = mHistory.remove(histsize - 1);
                mPlayPos = pos.intValue();
            } else {
                if (mPlayPos > 0) {
                    mPlayPos--;
                } else {
                    mPlayPos = mPlayListLen - 1;
                }
            }
            saveBookmarkIfNeeded();
            stop(false);
            openCurrent();
            play();
            notifyChange(META_CHANGED);
        }
    
public voidregisterExternalStorageListener()
Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The intent will call closeExternalStorageFiles() if the external media is going to be ejected, so applications can clean up any files they have open.

        if (mUnmountReceiver == null) {
            mUnmountReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
                        saveQueue(true);
                        mOneShot = true; // This makes us not save the state again later,
                                         // which would be wrong because the song ids and
                                         // card id might not match. 
                        closeExternalStorageFiles(intent.getData().getPath());
                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                        mMediaMountedCount++;
                        mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
                        reloadQueue();
                        notifyChange(QUEUE_CHANGED);
                        notifyChange(META_CHANGED);
                    }
                }
            };
            IntentFilter iFilter = new IntentFilter();
            iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
            iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
            iFilter.addDataScheme("file");
            registerReceiver(mUnmountReceiver, iFilter);
        }
    
private voidreloadQueue()

        String q = null;
        
        boolean newstyle = false;
        int id = mCardId;
        if (mPreferences.contains("cardid")) {
            newstyle = true;
            id = mPreferences.getInt("cardid", ~mCardId);
        }
        if (id == mCardId) {
            // Only restore the saved playlist if the card is still
            // the same one as when the playlist was saved
            q = mPreferences.getString("queue", "");
        }
        if (q != null && q.length() > 1) {
            //Log.i("@@@@ service", "loaded queue: " + q);
            String [] entries = q.split(";");
            int len = entries.length;
            ensurePlayListCapacity(len);
            for (int i = 0; i < len; i++) {
                if (newstyle) {
                    String revhex = entries[i];
                    int n = 0;
                    for (int j = revhex.length() - 1; j >= 0 ; j--) {
                        n <<= 4;
                        char c = revhex.charAt(j);
                        if (c >= '0" && c <= '9") {
                            n += (c - '0");
                        } else if (c >= 'a" && c <= 'f") {
                            n += (10 + c - 'a");
                        } else {
                            // bogus playlist data
                            len = 0;
                            break;
                        }
                    }
                    mPlayList[i] = n;
                } else {
                    mPlayList[i] = Integer.parseInt(entries[i]);
                }
            }
            mPlayListLen = len;

            int pos = mPreferences.getInt("curpos", 0);
            if (pos < 0 || pos >= len) {
                // The saved playlist is bogus, discard it
                mPlayListLen = 0;
                return;
            }
            mPlayPos = pos;
            
            // When reloadQueue is called in response to a card-insertion,
            // we might not be able to query the media provider right away.
            // To deal with this, try querying for the current file, and if
            // that fails, wait a while and try again. If that too fails,
            // assume there is a problem and don't restore the state.
            Cursor c = MusicUtils.query(this,
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
            if (c == null || c.getCount() == 0) {
                // wait a bit and try again
                SystemClock.sleep(3000);
                c = getContentResolver().query(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
            }
            if (c != null) {
                c.close();
            }

            // Make sure we don't auto-skip to the next song, since that
            // also starts playback. What could happen in that case is:
            // - music is paused
            // - go to UMS and delete some files, including the currently playing one
            // - come back from UMS
            // (time passes)
            // - music app is killed for some reason (out of memory)
            // - music service is restarted, service restores state, doesn't find
            //   the "current" file, goes to the next and: playback starts on its
            //   own, potentially at some random inconvenient time.
            mOpenFailedCounter = 20;
            mQuietMode = true;
            openCurrent();
            mQuietMode = false;
            if (!mPlayer.isInitialized()) {
                // couldn't restore the saved state
                mPlayListLen = 0;
                return;
            }
            
            long seekpos = mPreferences.getLong("seekpos", 0);
            seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
            
            int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
            if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
                repmode = REPEAT_NONE;
            }
            mRepeatMode = repmode;

            int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
            if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
                shufmode = SHUFFLE_NONE;
            }
            if (shufmode == SHUFFLE_AUTO) {
                if (! makeAutoShuffleList()) {
                    shufmode = SHUFFLE_NONE;
                }
            }
            mShuffleMode = shufmode;
        }
    
public intremoveTrack(int id)
Removes all instances of the track with the given id from the playlist.

param
id The id to be removed
return
how many instances of the track were removed

        int numremoved = 0;
        synchronized (this) {
            for (int i = 0; i < mPlayListLen; i++) {
                if (mPlayList[i] == id) {
                    numremoved += removeTracksInternal(i, i);
                    i--;
                }
            }
        }
        if (numremoved > 0) {
            notifyChange(QUEUE_CHANGED);
        }
        return numremoved;
    
public intremoveTracks(int first, int last)
Removes the range of tracks specified from the play list. If a file within the range is the file currently being played, playback will move to the next file after the range.

param
first The first file to be removed
param
last The last file to be removed
return
the number of tracks deleted

        int numremoved = removeTracksInternal(first, last);
        if (numremoved > 0) {
            notifyChange(QUEUE_CHANGED);
        }
        return numremoved;
    
private intremoveTracksInternal(int first, int last)

        synchronized (this) {
            if (last < first) return 0;
            if (first < 0) first = 0;
            if (last >= mPlayListLen) last = mPlayListLen - 1;

            boolean gotonext = false;
            if (first <= mPlayPos && mPlayPos <= last) {
                mPlayPos = first;
                gotonext = true;
            } else if (mPlayPos > last) {
                mPlayPos -= (last - first + 1);
            }
            int num = mPlayListLen - last - 1;
            for (int i = 0; i < num; i++) {
                mPlayList[first + i] = mPlayList[last + 1 + i];
            }
            mPlayListLen -= last - first + 1;
            
            if (gotonext) {
                if (mPlayListLen == 0) {
                    stop(true);
                    mPlayPos = -1;
                } else {
                    if (mPlayPos >= mPlayListLen) {
                        mPlayPos = 0;
                    }
                    boolean wasPlaying = isPlaying();
                    stop(false);
                    openCurrent();
                    if (wasPlaying) {
                        play();
                    }
                }
            }
            return last - first + 1;
        }
    
private voidsaveBookmarkIfNeeded()

        try {
            if (isPodcast()) {
                long pos = position();
                long bookmark = getBookmark();
                long duration = duration();
                if ((pos < bookmark && (pos + 10000) > bookmark) ||
                        (pos > bookmark && (pos - 10000) < bookmark)) {
                    // The existing bookmark is close to the current
                    // position, so don't update it.
                    return;
                }
                if (pos < 15000 || (pos + 10000) > duration) {
                    // if we're near the start or end, clear the bookmark
                    pos = 0;
                }
                
                // write 'pos' to the bookmark field
                ContentValues values = new ContentValues();
                values.put(MediaStore.Audio.Media.BOOKMARK, pos);
                Uri uri = ContentUris.withAppendedId(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
                getContentResolver().update(uri, values, null, null);
            }
        } catch (SQLiteException ex) {
        }
    
private voidsaveQueue(boolean full)


        
        if (mOneShot) {
            return;
        }
        Editor ed = mPreferences.edit();
        //long start = System.currentTimeMillis();
        if (full) {
            StringBuilder q = new StringBuilder();
            
            // The current playlist is saved as a list of "reverse hexadecimal"
            // numbers, which we can generate faster than normal decimal or
            // hexadecimal numbers, which in turn allows us to save the playlist
            // more often without worrying too much about performance.
            // (saving the full state takes about 40 ms under no-load conditions
            // on the phone)
            int len = mPlayListLen;
            for (int i = 0; i < len; i++) {
                int n = mPlayList[i];
                if (n == 0) {
                    q.append("0;");
                } else {
                    while (n != 0) {
                        int digit = n & 0xf;
                        n >>= 4;
                        q.append(hexdigits[digit]);
                    }
                    q.append(";");
                }
            }
            //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
            ed.putString("queue", q.toString());
            ed.putInt("cardid", mCardId);
        }
        ed.putInt("curpos", mPlayPos);
        if (mPlayer.isInitialized()) {
            ed.putLong("seekpos", mPlayer.position());
        }
        ed.putInt("repeatmode", mRepeatMode);
        ed.putInt("shufflemode", mShuffleMode);
        ed.commit();
  
        //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
    
public longseek(long pos)
Seeks to the position specified.

param
pos The position to seek to, in milliseconds

        if (mPlayer.isInitialized()) {
            if (pos < 0) pos = 0;
            if (pos > mPlayer.duration()) pos = mPlayer.duration();
            return mPlayer.seek(pos);
        }
        return -1;
    
public voidsetQueuePosition(int pos)
Starts playing the track at the given position in the queue.

param
pos The position in the queue of the track that will be played.

        synchronized(this) {
            stop(false);
            mPlayPos = pos;
            openCurrent();
            play();
            notifyChange(META_CHANGED);
        }
    
public voidsetRepeatMode(int repeatmode)

        synchronized(this) {
            mRepeatMode = repeatmode;
            saveQueue(false);
        }
    
public voidsetShuffleMode(int shufflemode)

        synchronized(this) {
            if (mShuffleMode == shufflemode && mPlayListLen > 0) {
                return;
            }
            mShuffleMode = shufflemode;
            if (mShuffleMode == SHUFFLE_AUTO) {
                if (makeAutoShuffleList()) {
                    mPlayListLen = 0;
                    doAutoShuffleUpdate();
                    mPlayPos = 0;
                    openCurrent();
                    play();
                    notifyChange(META_CHANGED);
                    return;
                } else {
                    // failed to build a list of files to shuffle
                    mShuffleMode = SHUFFLE_NONE;
                }
            }
            saveQueue(false);
        }
    
private voidstartAndFadeIn()

    
       
        mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
    
private voidstop(boolean remove_status_icon)

        if (mPlayer.isInitialized()) {
            mPlayer.stop();
        }
        mFileToPlay = null;
        if (mCursor != null) {
            mCursor.close();
            mCursor = null;
        }
        if (remove_status_icon) {
            gotoIdleState();
        }
        setForeground(false);
        if (remove_status_icon) {
            mWasPlaying = false;
        }
    
public voidstop()
Stops playback.

        stop(true);