diff --git a/res/values/translatable.xml b/res/values/translatable.xml index 35fc6d99..dd1b1c00 100644 --- a/res/values/translatable.xml +++ b/res/values/translatable.xml @@ -29,6 +29,7 @@ THE SOFTWARE. Settings Library Display Mode + Random enabled Failed to load song %s. It may be corrupt or missing. Queue cleared. diff --git a/src/org/kreed/vanilla/FullPlaybackActivity.java b/src/org/kreed/vanilla/FullPlaybackActivity.java index 1c8d84bb..661fd3f7 100644 --- a/src/org/kreed/vanilla/FullPlaybackActivity.java +++ b/src/org/kreed/vanilla/FullPlaybackActivity.java @@ -236,15 +236,21 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On if ((state & PlaybackService.FLAG_PLAYING) != 0) updateProgress(); - if ((toggled & (PlaybackService.FLAG_REPEAT|PlaybackService.FLAG_REPEAT_CURRENT|PlaybackService.FLAG_RANDOM)) != 0) { - if ((state & PlaybackService.FLAG_REPEAT) != 0) - mEndButton.setImageResource(R.drawable.repeat_active); - else if ((state & PlaybackService.FLAG_REPEAT_CURRENT) != 0) - mEndButton.setImageResource(R.drawable.repeat_current_active); - else if ((state & PlaybackService.FLAG_RANDOM) != 0) - mEndButton.setImageResource(R.drawable.random_active); - else + if ((toggled & PlaybackService.MASK_FINISH) != 0) { + switch (PlaybackService.finishAction(state)) { + case SongTimeline.FINISH_STOP: mEndButton.setImageResource(R.drawable.repeat_inactive); + break; + case SongTimeline.FINISH_REPEAT: + mEndButton.setImageResource(R.drawable.repeat_active); + break; + case SongTimeline.FINISH_REPEAT_CURRENT: + mEndButton.setImageResource(R.drawable.repeat_current_active); + break; + case SongTimeline.FINISH_RANDOM: + mEndButton.setImageResource(R.drawable.random_active); + break; + } } if ((toggled & PlaybackService.MASK_SHUFFLE) != 0) { @@ -455,10 +461,7 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On { switch (view.getId()) { case R.id.end_action: - if ((mState & (PlaybackService.FLAG_REPEAT_CURRENT|PlaybackService.FLAG_RANDOM)) != 0) - toggleRandom(); - else - cycleRepeat(); + cycleFinishAction(); break; case R.id.shuffle: cycleShuffle(); diff --git a/src/org/kreed/vanilla/PlaybackActivity.java b/src/org/kreed/vanilla/PlaybackActivity.java index b12fc726..97402de2 100644 --- a/src/org/kreed/vanilla/PlaybackActivity.java +++ b/src/org/kreed/vanilla/PlaybackActivity.java @@ -54,7 +54,6 @@ public class PlaybackActivity extends Activity public static final int ACTION_PREVIOUS_SONG = 4; public static final int ACTION_REPEAT = 5; public static final int ACTION_SHUFFLE = 6; - public static final int ACTION_RANDOM = 7; public static final int ACTION_ENQUEUE_ALBUM = 8; public static final int ACTION_ENQUEUE_ARTIST = 9; public static final int ACTION_ENQUEUE_GENRE = 10; @@ -347,19 +346,11 @@ public class PlaybackActivity extends Activity } /** - * Cycle repeat mode. + * Cycle the finish action. */ - public void cycleRepeat() + public void cycleFinishAction() { - setState(PlaybackService.get(this).cycleRepeat()); - } - - /** - * Toggle random mode on/off - */ - public void toggleRandom() - { - setState(PlaybackService.get(this).toggleRandom()); + setState(PlaybackService.get(this).cycleFinishAction()); } /** @@ -393,14 +384,11 @@ public class PlaybackActivity extends Activity previousSong(); break; case ACTION_REPEAT: - cycleRepeat(); + cycleFinishAction(); break; case ACTION_SHUFFLE: cycleShuffle(); break; - case ACTION_RANDOM: - toggleRandom(); - break; case ACTION_ENQUEUE_ALBUM: enqueue(MediaUtils.TYPE_ALBUM); break; diff --git a/src/org/kreed/vanilla/PlaybackService.java b/src/org/kreed/vanilla/PlaybackService.java index 60fe7148..f7929c6d 100644 --- a/src/org/kreed/vanilla/PlaybackService.java +++ b/src/org/kreed/vanilla/PlaybackService.java @@ -73,7 +73,7 @@ public final class PlaybackService extends Service implements Handler.Callback, /** * State file version that indicates data order. */ - private static final int STATE_VERSION = 3; + private static final int STATE_VERSION = 4; private static final int NOTIFICATION_ID = 2; @@ -138,33 +138,40 @@ public final class PlaybackService extends Service implements Handler.Callback, * Set when there is no media available on the device. */ public static final int FLAG_NO_MEDIA = 0x2; - /** - * If set, will loop back to the beginning of the timeline when its end is - * reached. - */ - public static final int FLAG_REPEAT = 0x8; /** * Set when the current song is unplayable. */ - public static final int FLAG_ERROR = 0x10; - /** - * If set, random songs will be added to the timeline when its end is - * reached. Overrides FLAG_REPEAT. - */ - public static final int FLAG_RANDOM = 0x20; + public static final int FLAG_ERROR = 0x4; /** * Set when the user needs to select songs to play. */ - public static final int FLAG_EMPTY_QUEUE = 0x40; + public static final int FLAG_EMPTY_QUEUE = 0x8; + public static final int SHIFT_FINISH = 4; /** - * If set, replay the current song when the end of the song is reached - * instead of advancing to the next song. + * These two bits will be one of SongTimeline.FINISH_*. */ - public static final int FLAG_REPEAT_CURRENT = 0x80; + public static final int MASK_FINISH = 0x3 << SHIFT_FINISH; + public static final int SHIFT_SHUFFLE = 6; /** * These two bits will be one of SongTimeline.SHUFFLE_*. */ - public static final int MASK_SHUFFLE = 0x100 | 0x200; + public static final int MASK_SHUFFLE = 0x3 << SHIFT_SHUFFLE; + + /** + * The PlaybackService state, indicating if the service is playing, + * repeating, etc. + * + * The format of this is 0b00000000_00000000_00000000_ffeedcba, + * where each bit is: + * a: {@link PlaybackService#FLAG_PLAYING} + * b: {@link PlaybackService#FLAG_NO_MEDIA} + * c: {@link PlaybackService#FLAG_ERROR} + * d: {@link PlaybackService#FLAG_EMPTY_QUEUE} + * ee: {@link PlaybackService#MASK_FINISH} + * ff: {@link PlaybackService#MASK_SHUFFLE} + */ + int mState; + private final Object mStateLock = new Object[0]; public static final int NEVER = 0; public static final int WHEN_PLAYING = 1; @@ -235,10 +242,8 @@ public final class PlaybackService extends Service implements Handler.Callback, private AudioManager mAudioManager; SongTimeline mTimeline; - int mState; private Song mCurrentSong; - private final Object mStateLock = new Object(); boolean mPlayingBeforeCall; private int mPendingSeek; public Receiver mReceiver; @@ -524,16 +529,8 @@ public final class PlaybackService extends Service implements Handler.Callback, if ((toggled & MASK_SHUFFLE) != 0) mTimeline.setShuffleMode(shuffleMode(state)); - if ((toggled & (FLAG_REPEAT | FLAG_RANDOM)) != 0) { - int action; - if ((state & FLAG_RANDOM) != 0) - action = SongTimeline.FINISH_RANDOM; - else if ((state & FLAG_REPEAT) != 0) - action = SongTimeline.FINISH_REPEAT; - else - action = SongTimeline.FINISH_STOP; - mTimeline.setFinishAction(action); - } + if ((toggled & MASK_FINISH) != 0) + mTimeline.setFinishAction(finishAction(state)); } private void broadcastChange(int state, Song song, long uptime) @@ -627,8 +624,9 @@ public final class PlaybackService extends Service implements Handler.Callback, { synchronized (mStateLock) { if ((mState & FLAG_EMPTY_QUEUE) != 0) { - updateState((mState | FLAG_RANDOM) & ~FLAG_REPEAT); + setFinishAction(SongTimeline.FINISH_RANDOM); setCurrentSong(0); + Toast.makeText(this, R.string.random_enabling, Toast.LENGTH_SHORT).show(); } int state = updateState(mState | FLAG_PLAYING); @@ -666,15 +664,17 @@ public final class PlaybackService extends Service implements Handler.Callback, } } + /** - * Toggle random mode. Disables repeat mode. + * Change the end action (e.g. repeat, random). * + * @param action The new action. One of SongTimeline.FINISH_*. * @return The new state after this is called. */ - public int toggleRandom() + public int setFinishAction(int action) { synchronized (mStateLock) { - return updateState((mState ^ FLAG_RANDOM) & ~(FLAG_REPEAT|FLAG_REPEAT_CURRENT)); + return updateState((mState & ~MASK_FINISH) | (action << SHIFT_FINISH)); } } @@ -683,17 +683,26 @@ public final class PlaybackService extends Service implements Handler.Callback, * * @return The new state after this is called. */ - public int cycleRepeat() + public int cycleFinishAction() { synchronized (mStateLock) { - int state = mState & ~FLAG_RANDOM; - if ((state & FLAG_REPEAT_CURRENT) != 0) - state &= ~(FLAG_REPEAT|FLAG_REPEAT_CURRENT); - else if ((state & FLAG_REPEAT) == 0) - state |= FLAG_REPEAT; - else if ((state & FLAG_REPEAT) != 0) - state = (state | FLAG_REPEAT_CURRENT) & ~FLAG_REPEAT; - return updateState(state); + int mode = finishAction(mState) + 1; + if (mode > SongTimeline.FINISH_RANDOM) + mode = SongTimeline.FINISH_STOP; + return setFinishAction(mode); + } + } + + /** + * Change the shuffle mode. + * + * @param mode The new mode. One of SongTimeline.SHUFFLE_*. + * @return The new state after this is called. + */ + public int setShuffleMode(int mode) + { + synchronized (mStateLock) { + return updateState((mState & ~MASK_SHUFFLE) | (mode << SHIFT_SHUFFLE)); } } @@ -705,10 +714,10 @@ public final class PlaybackService extends Service implements Handler.Callback, public int cycleShuffle() { synchronized (mStateLock) { - int state = mState; - int step = (state & MASK_SHUFFLE) == 0x200 ? 0x200 : 0x100; - state = (state & ~MASK_SHUFFLE) | ((state + step) & MASK_SHUFFLE); - return updateState(state); + int mode = shuffleMode(mState) + 1; + if (mode > SongTimeline.SHUFFLE_ALBUMS) + mode = SongTimeline.SHUFFLE_NONE; + return setShuffleMode(mode); } } @@ -729,7 +738,7 @@ public final class PlaybackService extends Service implements Handler.Callback, mCurrentSong = song; if (song == null || song.id == -1 || song.path == null) { if (MediaUtils.isSongAvailable(getContentResolver())) { - int flag = (mState & FLAG_RANDOM) == 0 ? FLAG_EMPTY_QUEUE : FLAG_ERROR; + int flag = finishAction(mState) == SongTimeline.FINISH_RANDOM ? FLAG_ERROR : FLAG_EMPTY_QUEUE; synchronized (mStateLock) { updateState((mState | flag) & ~FLAG_NO_MEDIA); } @@ -790,10 +799,10 @@ public final class PlaybackService extends Service implements Handler.Callback, @Override public void onCompletion(MediaPlayer player) { - if (mTimeline.isEndOfQueue()) - unsetFlag(FLAG_PLAYING); - else if ((mState & FLAG_REPEAT_CURRENT) != 0) + if (finishAction(mState) == SongTimeline.FINISH_REPEAT_CURRENT) setCurrentSong(0); + else if (mTimeline.isEndOfQueue()) + unsetFlag(FLAG_PLAYING); else setCurrentSong(+1); } @@ -1284,16 +1293,9 @@ public final class PlaybackService extends Service implements Handler.Callback, if (in.readLong() == STATE_FILE_MAGIC && in.readInt() == STATE_VERSION) { mPendingSeek = in.readInt(); - int savedState = 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; - state |= mTimeline.getShuffleMode() << 8; - state |= savedState & FLAG_REPEAT_CURRENT; + state |= mTimeline.getShuffleMode() << SHIFT_SHUFFLE; + state |= mTimeline.getFinishAction() << SHIFT_FINISH; } in.close(); @@ -1319,7 +1321,6 @@ public final class PlaybackService extends Service implements Handler.Callback, out.writeLong(STATE_FILE_MAGIC); out.writeInt(STATE_VERSION); out.writeInt(pendingSeek); - out.writeInt(mState); mTimeline.writeState(out); out.close(); } catch (IOException e) { @@ -1330,11 +1331,23 @@ public final class PlaybackService extends Service implements Handler.Callback, /** * Returns the shuffle mode for the given state. * + * @param state The PlaybackService state to process. * @return The shuffle mode. One of SongTimeline.SHUFFLE_*. */ public static int shuffleMode(int state) { - return (state & MASK_SHUFFLE) >> 8; + return (state & MASK_SHUFFLE) >> SHIFT_SHUFFLE; + } + + /** + * Returns the finish action for the given state. + * + * @param state The PlaybackService state to process. + * @return The finish action. One of SongTimeline.FINISH_*. + */ + public static int finishAction(int state) + { + return (state & MASK_FINISH) >> SHIFT_FINISH; } /** diff --git a/src/org/kreed/vanilla/SongTimeline.java b/src/org/kreed/vanilla/SongTimeline.java index 5336e191..e66161db 100644 --- a/src/org/kreed/vanilla/SongTimeline.java +++ b/src/org/kreed/vanilla/SongTimeline.java @@ -55,12 +55,21 @@ public final class SongTimeline { * @see SongTimeline#setFinishAction(int) */ public static final int FINISH_REPEAT = 1; + /** + * Repeat the current song. This behavior is implemented entirely in + * {@link PlaybackService#onCompletion(android.media.MediaPlayer)}; + * pressing the next or previous buttons will advance the song as normal; + * only allowing the song to play until the end will repeat it. + * + * @see SongTimeline#setFinishAction(int) + */ + public static final int FINISH_REPEAT_CURRENT = 2; /** * Add random songs to the playlist. * * @see SongTimeline#setFinishAction(int) */ - public static final int FINISH_RANDOM = 2; + public static final int FINISH_RANDOM = 3; /** * Clear the timeline and use only the provided songs. @@ -397,6 +406,7 @@ public final class SongTimeline { switch (mFinishAction) { case FINISH_STOP: case FINISH_REPEAT: + case FINISH_REPEAT_CURRENT: if (size == 0) // empty queue return null;