From 5341772d0cea85ecea5b71981c3cd64d9e41a35a Mon Sep 17 00:00:00 2001 From: Christopher Eby Date: Wed, 21 Sep 2011 17:32:38 -0500 Subject: [PATCH] Move state file code to PlaybackService and add a version code to state file --- src/org/kreed/vanilla/PlaybackService.java | 87 ++++++++-- src/org/kreed/vanilla/SongTimeline.java | 185 ++++++++------------- 2 files changed, 148 insertions(+), 124 deletions(-) diff --git a/src/org/kreed/vanilla/PlaybackService.java b/src/org/kreed/vanilla/PlaybackService.java index c6aea6c6..d7a2db22 100644 --- a/src/org/kreed/vanilla/PlaybackService.java +++ b/src/org/kreed/vanilla/PlaybackService.java @@ -22,6 +22,9 @@ package org.kreed.vanilla; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; import java.io.IOException; import java.util.ArrayList; @@ -50,6 +53,20 @@ import android.util.Log; import android.widget.Toast; public final class PlaybackService extends Service implements Handler.Callback, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, SharedPreferences.OnSharedPreferenceChangeListener, SongTimeline.Callback { + /** + * Name of the state file. + */ + private static final String STATE_FILE = "state"; + /** + * Header for state file to help indicate if the file is in the right + * format. + */ + private static final long STATE_FILE_MAGIC = 0x1533574DC74B6ECL; + /** + * State file version that indicates data order. + */ + private static final int STATE_VERSION = 1; + private static final int NOTIFICATION_ID = 2; /** @@ -186,7 +203,7 @@ public final class PlaybackService extends Service implements Handler.Callback, mTimeline = new SongTimeline(this); mTimeline.setCallback(this); - mPendingSeek = mTimeline.loadState(); + int state = loadState(); mMediaPlayer = new MediaPlayer(); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); @@ -218,14 +235,6 @@ public final class PlaybackService extends Service implements Handler.Callback, initWidgets(); - int state = 0; - int finishAction = mTimeline.getFinishAction(); - if (finishAction == SongTimeline.FINISH_RANDOM) - state |= FLAG_RANDOM; - else if (finishAction == SongTimeline.FINISH_REPEAT) - state |= FLAG_REPEAT; - if (mTimeline.isShuffling()) - state |= FLAG_SHUFFLE; updateState(state); setCurrentSong(0); @@ -285,7 +294,7 @@ public final class PlaybackService extends Service implements Handler.Callback, stopForeground(true); if (mMediaPlayer != null) { - mTimeline.saveState(mMediaPlayer.getCurrentPosition()); + saveState(mMediaPlayer.getCurrentPosition()); mMediaPlayer.release(); mMediaPlayer = null; } @@ -826,7 +835,7 @@ public final class PlaybackService extends Service implements Handler.Callback, case SAVE_STATE: // For unexpected terminations: crashes, task killers, etc. // In most cases onDestroy will handle this - mTimeline.saveState(0); + saveState(0); break; case PROCESS_SONG: processSong((Song)message.obj); @@ -1135,4 +1144,60 @@ public final class PlaybackService extends Service implements Handler.Callback, { sActivities.remove(activity); } + + /** + * Initializes the service state, loading songs saved from the disk into the + * song timeline. + * + * @return The loaded value for mState. + */ + public int loadState() + { + int state = 0; + + try { + DataInputStream in = new DataInputStream(openFileInput(STATE_FILE)); + + if (in.readLong() == STATE_FILE_MAGIC && in.readInt() == STATE_VERSION) { + mPendingSeek = in.readInt(); + mTimeline.readState(in); + + int finishAction = mTimeline.getFinishAction(); + if (finishAction == SongTimeline.FINISH_RANDOM) + state |= FLAG_RANDOM; + else if (finishAction == SongTimeline.FINISH_REPEAT) + state |= FLAG_REPEAT; + if (mTimeline.isShuffling()) + state |= FLAG_SHUFFLE; + } + + in.close(); + } catch (EOFException e) { + Log.w("VanillaMusic", "Failed to load state", e); + } catch (IOException e) { + Log.w("VanillaMusic", "Failed to load state", e); + } + + return state; + } + + /** + * Save the service state to disk. + * + * @param pendingSeek The pendingSeek to store. Should be the current + * MediaPlayer position or 0. + */ + public void saveState(int pendingSeek) + { + try { + DataOutputStream out = new DataOutputStream(openFileOutput(STATE_FILE, 0)); + out.writeLong(STATE_FILE_MAGIC); + out.writeInt(STATE_VERSION); + out.writeInt(pendingSeek); + mTimeline.writeState(out); + out.close(); + } catch (IOException e) { + Log.w("VanillaMusic", "Failed to save state", e); + } + } } diff --git a/src/org/kreed/vanilla/SongTimeline.java b/src/org/kreed/vanilla/SongTimeline.java index cd6a0cfb..9d50261f 100644 --- a/src/org/kreed/vanilla/SongTimeline.java +++ b/src/org/kreed/vanilla/SongTimeline.java @@ -36,7 +36,6 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; -import android.util.Log; /** * Represents a series of songs that can be moved through backward or forward. @@ -82,23 +81,12 @@ public final class SongTimeline { */ public static final int MODE_ENQUEUE = 2; - /** - * Name of the state file. - */ - private static final String STATE_FILE = "state"; - /** - * Header for state file to help indicate if the file is in the right - * format. - */ - private static final long STATE_FILE_MAGIC = 0xf89daa2fac33L; - private Context mContext; - /** * All the songs currently contained in the timeline. Each Song object * should be unique, even if it refers to the same media. */ - private ArrayList mSongs; + private ArrayList mSongs = new ArrayList(); /** * The position of the current song (i.e. the playing song). */ @@ -176,133 +164,104 @@ public final class SongTimeline { } /** - * Initializes the timeline with the state stored in the state file created - * by a call to save state. + * Initializes the timeline with data read from the stream. Data should have + * been saved by a call to {@link SongTimeline#writeState(DataOutputStream)}. * - * @return The optional extra data, or -1 if loading failed + * @param in The stream to read from. */ - public int loadState() + public void readState(DataInputStream in) throws IOException, EOFException { - int extra = -1; + synchronized (this) { + int n = in.readInt(); + if (n > 0) { + ArrayList songs = new ArrayList(n); - try { - synchronized (this) { - DataInputStream in = new DataInputStream(mContext.openFileInput(STATE_FILE)); - if (in.readLong() == STATE_FILE_MAGIC) { - int n = in.readInt(); - if (n > 0) { - ArrayList songs = new ArrayList(n); + // Fill the selection with the ids of all the saved songs + // and initialize the timeline with unpopulated songs. + StringBuilder selection = new StringBuilder("_ID IN ("); + for (int i = 0; i != n; ++i) { + long id = in.readLong(); + int flags = in.readInt(); + // Add the index so we can sort + flags |= i << Song.FLAG_COUNT; + songs.add(new Song(id, flags)); - // Fill the selection with the ids of all the saved songs - // and initialize the timeline with unpopulated songs. - StringBuilder selection = new StringBuilder("_ID IN ("); - for (int i = 0; i != n; ++i) { - long id = in.readLong(); - int flags = in.readInt(); - // Add the index so we can sort - flags |= i << Song.FLAG_COUNT; - songs.add(new Song(id, flags)); + if (i != 0) + selection.append(','); + selection.append(id); + } + selection.append(')'); - if (i != 0) - selection.append(','); - selection.append(id); - } - selection.append(')'); + // Sort songs by id---this is the order the query will + // return its results in. + Collections.sort(songs, new IdComparator()); - // Sort songs by id---this is the order the query will - // return its results in. - Collections.sort(songs, new IdComparator()); + ContentResolver resolver = mContext.getContentResolver(); + Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - ContentResolver resolver = mContext.getContentResolver(); - Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + Cursor cursor = resolver.query(media, Song.FILLED_PROJECTION, selection.toString(), null, "_id"); + if (cursor != null) { + cursor.moveToNext(); - Cursor cursor = resolver.query(media, Song.FILLED_PROJECTION, selection.toString(), null, "_id"); - if (cursor != null) { + // Loop through timeline entries, looking for a row + // that matches the id. One row may match multiple + // entries. + Iterator it = songs.iterator(); + while (it.hasNext()) { + Song e = it.next(); + while (cursor.getLong(0) < e.id) cursor.moveToNext(); - - // Loop through timeline entries, looking for a row - // that matches the id. One row may match multiple - // entries. - Iterator it = songs.iterator(); - while (it.hasNext()) { - Song e = it.next(); - while (cursor.getLong(0) < e.id) - cursor.moveToNext(); - if (cursor.getLong(0) == e.id) - e.populate(cursor); - else - // We weren't able to query this song. - it.remove(); - } - - cursor.close(); - - // Revert to the order the songs were saved in. - Collections.sort(songs, new FlagComparator()); - - mSongs = songs; - } + if (cursor.getLong(0) == e.id) + e.populate(cursor); + else + // We weren't able to query this song. + it.remove(); } - mCurrentPos = Math.min(mSongs == null ? 0 : mSongs.size(), in.readInt()); - mFinishAction = in.readInt(); - mShuffle = in.readBoolean(); - extra = in.readInt(); + cursor.close(); - in.close(); + // Revert to the order the songs were saved in. + Collections.sort(songs, new FlagComparator()); + + mSongs = songs; } } - } catch (EOFException e) { - Log.w("VanillaMusic", "Failed to load state", e); - } catch (IOException e) { - Log.w("VanillaMusic", "Failed to load state", e); + + mCurrentPos = Math.min(mSongs == null ? 0 : mSongs.size(), in.readInt()); + mFinishAction = in.readInt(); + mShuffle = in.readBoolean(); } - - if (mSongs == null) - mSongs = new ArrayList(); - - return extra; } /** - * Returns a byte array representing the current state of the timeline. - * This can be passed to the appropriate constructor to initialize the - * timeline with this state. + * Writes the current songs and state to the given stream. * - * @param extra Optional extra data to be included. Should not be -1. + * @param out The stream to write to. */ - public void saveState(int extra) + public void writeState(DataOutputStream out) throws IOException { - try { - DataOutputStream out = new DataOutputStream(mContext.openFileOutput(STATE_FILE, 0)); - out.writeLong(STATE_FILE_MAGIC); + // Must update PlaybackService.STATE_VERSION when changing behavior + // here. + synchronized (this) { + ArrayList songs = mSongs; - synchronized (this) { - ArrayList songs = mSongs; + int size = songs.size(); + out.writeInt(size); - int size = songs.size(); - out.writeInt(size); - - for (int i = 0; i != size; ++i) { - Song song = songs.get(i); - if (song == null) { - out.writeLong(-1); - out.writeInt(0); - } else { - out.writeLong(song.id); - out.writeInt(song.flags); - } + for (int i = 0; i != size; ++i) { + Song song = songs.get(i); + if (song == null) { + out.writeLong(-1); + out.writeInt(0); + } else { + out.writeLong(song.id); + out.writeInt(song.flags); } - - out.writeInt(mCurrentPos); - out.writeInt(mFinishAction); - out.writeBoolean(mShuffle); - out.writeInt(extra); } - out.close(); - } catch (IOException e) { - Log.w("VanillaMusic", "Failed to save state", e); + out.writeInt(mCurrentPos); + out.writeInt(mFinishAction); + out.writeBoolean(mShuffle); } }