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;