Restructure play/pause and song change to update UI immediately

PlaybackService methods return the new song or state immediately, the calling
Activity updates with the result, the rest of the work is done in the
background, and inactive activities update with the broadcast.

Previously, everything was done in the background and the UI did
not update until the work was done and the update was broadcast, which took
forever.
This commit is contained in:
Christopher Eby 2011-08-28 18:11:53 -05:00
parent 7336b69e08
commit 13e30746ed
9 changed files with 280 additions and 256 deletions

View File

@ -58,6 +58,13 @@ public final class CoverView extends View implements Handler.Callback {
*/
private int mCoverStyle;
public interface Callback {
public void nextSong();
public void previousSong();
}
private Callback mCallback;
/**
* The current set of songs: previous, current, and next.
*/
@ -93,40 +100,26 @@ public final class CoverView extends View implements Handler.Callback {
}
/**
* Setup the Handler to act on the given looper. This must be called before
* Setup the Handler and callback. This must be called before
* the CoverView is used.
*
* @param looper The Looper to use.
* @param looper A looper created on a worker thread.
* @param callback The callback for nextSong/previousSong
* @param style One of CoverBitmap.STYLE_*
*/
public void setupHandler(Looper looper)
public void setup(Looper looper, Callback callback, int style)
{
mHandler = new Handler(looper, this);
}
/**
* Set the cover style. Must be one of CoverBitmap.STYLE_. Will only apply
* to bitmaps rendered after this method is called.
*/
public void setCoverStyle(int style)
{
mCallback = callback;
mCoverStyle = style;
}
/**
* Query the service for initial song info.
*/
public void initialize()
{
mTimelinePos = ContextApplication.getService().getTimelinePos();
querySongs(true);
}
/**
* Move to the next or previous song.
*
* @param delta -1 or 1, indicate the previous or next song, respectively
*/
public void go(int delta)
private void go(int delta)
{
int i = delta > 0 ? STORE_SIZE - 1 : 0;
int from = delta > 0 ? 1 : 0;
@ -134,7 +127,6 @@ public final class CoverView extends View implements Handler.Callback {
System.arraycopy(mSongs, from, mSongs, to, STORE_SIZE - 1);
mSongs[i] = null;
mTimelinePos += delta;
resetScroll();
invalidate();
}
@ -314,8 +306,11 @@ public final class CoverView extends View implements Handler.Callback {
} else if (mTentativeCover != -1) {
int delta = mTentativeCover - 1;
mTentativeCover = -1;
mHandler.sendMessage(mHandler.obtainMessage(PlaybackActivity.MSG_SET_SONG, delta, 0));
go(delta);
if (delta == 1)
mCallback.nextSong();
else
mCallback.previousSong();
resetScroll();
}
}
@ -363,6 +358,22 @@ public final class CoverView extends View implements Handler.Callback {
}
}
public void setCurrentSong(Song song)
{
mTimelinePos = ContextApplication.getService().getTimelinePos();
for (int delta = -STORE_SIZE / 2; delta <= STORE_SIZE / 2; ++delta) {
if (mSongs[delta + STORE_SIZE / 2] == song) {
if (delta != 0)
go(delta);
querySongs(false);
return;
}
}
querySongs(true);
}
/**
* Handle an intent broadcasted by the PlaybackService. This must be called
* to react to song changes in PlaybackService.
@ -375,12 +386,6 @@ public final class CoverView extends View implements Handler.Callback {
if (PlaybackService.EVENT_REPLACE_SONG.equals(action)) {
int i = STORE_SIZE / 2 + intent.getIntExtra("pos", 0);
setSong(i, (Song)intent.getParcelableExtra("song"));
} else if (PlaybackService.EVENT_CHANGED.equals(action)) {
mTimelinePos = intent.getIntExtra("pos", 0);
Song currentSong = mSongs[STORE_SIZE / 2];
Song playingSong = intent.getParcelableExtra("song");
boolean force = currentSong == null || !currentSong.equals(playingSong);
querySongs(force);
}
}
@ -390,13 +395,6 @@ public final class CoverView extends View implements Handler.Callback {
* obj must be the Song to generate a bitmap for.
*/
private static final int MSG_GENERATE_BITMAP = 0;
/**
* Tell PlaybackService to change the current song.
*
* arg1 should be the delta, -1 or 1, indicating the previous or next song,
* respectively.
*/
static final int MSG_SET_SONG = 1;
/**
* Perform a long click.
*
@ -410,12 +408,6 @@ public final class CoverView extends View implements Handler.Callback {
case MSG_GENERATE_BITMAP:
generateBitmap((Song)message.obj);
break;
case MSG_SET_SONG:
if (message.arg1 == 1)
ContextApplication.getService().nextSong();
else
ContextApplication.getService().previousSong();
break;
case MSG_LONG_CLICK:
mIgnoreNextUp = true;
performLongClick();

View File

@ -74,7 +74,11 @@ public class FourLongWidget extends AppWidgetProvider {
String action = intent.getAction();
if (PlaybackService.EVENT_CHANGED.equals(action) || PlaybackService.EVENT_REPLACE_SONG.equals(action)) {
Context context = ContextApplication.getContext();
Song song = intent.getParcelableExtra("song");
Song song;
if (intent.hasExtra("song"))
song = intent.getParcelableExtra("song");
else
song = ContextApplication.getService().getSong(0);
int state = intent.getIntExtra("state", -1);
AppWidgetManager manager = AppWidgetManager.getInstance(context);

View File

@ -74,7 +74,11 @@ public class FourSquareWidget extends AppWidgetProvider {
String action = intent.getAction();
if (PlaybackService.EVENT_CHANGED.equals(action) || PlaybackService.EVENT_REPLACE_SONG.equals(action)) {
Context context = ContextApplication.getContext();
Song song = intent.getParcelableExtra("song");
Song song;
if (intent.hasExtra("song"))
song = intent.getParcelableExtra("song");
else
song = ContextApplication.getService().getSong(0);
int state = intent.getIntExtra("state", -1);
AppWidgetManager manager = AppWidgetManager.getInstance(context);

View File

@ -97,11 +97,9 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
setContentView(layout);
CoverView coverView = (CoverView)findViewById(R.id.cover_view);
coverView.setCoverStyle(coverStyle);
coverView.setup(mLooper, this, coverStyle);
coverView.setOnClickListener(this);
coverView.setOnLongClickListener(this);
coverView.setupHandler(mLooper);
mCoverView = coverView;
mControlsTop = findViewById(R.id.controls_top);
@ -189,34 +187,52 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
}
@Override
protected void setState(int state)
protected void setState(final int state)
{
if ((mState & PlaybackService.FLAG_NO_MEDIA) == 0 && (state & PlaybackService.FLAG_NO_MEDIA) != 0)
mUiHandler.sendEmptyMessage(MSG_SHOW_NO_MEDIA);
else if ((mState & PlaybackService.FLAG_NO_MEDIA) != 0 && (state & PlaybackService.FLAG_NO_MEDIA) == 0)
mUiHandler.sendEmptyMessage(MSG_HIDE_MESSAGE_OVERLAY);
int toggled = mState ^ state;
if ((toggled & PlaybackService.FLAG_NO_MEDIA) != 0) {
runOnUiThread(new Runnable() {
@Override
public void run()
{
if ((state & PlaybackService.FLAG_NO_MEDIA) != 0) {
showMessageOverlay();
setNoMediaOverlayMessage();
} else {
hideMessageOverlay();
}
}
});
}
super.setState(state);
}
@Override
protected void onServiceReady()
protected void onSongChange(final Song song)
{
super.onServiceReady();
Song song = ContextApplication.getService().getSong(0);
mUiHandler.sendMessage(mUiHandler.obtainMessage(MSG_UPDATE_SONG, song));
}
@Override
public void receive(Intent intent)
{
super.receive(intent);
if (PlaybackService.EVENT_CHANGED.equals(intent.getAction())) {
Song song = intent.getParcelableExtra("song");
mUiHandler.sendMessage(mUiHandler.obtainMessage(MSG_UPDATE_SONG, song));
mDuration = song == null ? 0 : song.duration;
if (mTitle != null) {
runOnUiThread(new Runnable() {
@Override
public void run()
{
if (song == null) {
mTitle.setText(null);
mAlbum.setText(null);
mArtist.setText(null);
} else {
mTitle.setText(song.title);
mAlbum.setText(song.album);
mArtist.setText(song.artist);
}
}
});
}
mUiHandler.sendEmptyMessage(MSG_UPDATE_PROGRESS);
super.onSongChange(song);
}
@Override
@ -245,18 +261,17 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_RIGHT:
nextSong();
findViewById(R.id.next).requestFocus();
mHandler.sendMessage(mHandler.obtainMessage(PlaybackActivity.MSG_SET_SONG, 1, 0));
mCoverView.go(1);
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
previousSong();
findViewById(R.id.previous).requestFocus();
mHandler.sendMessage(mHandler.obtainMessage(PlaybackActivity.MSG_SET_SONG, -1, 0));
mCoverView.go(-1);
return true;
}
@ -349,7 +364,7 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
public boolean onLongClick(View view)
{
if (view.getId() == R.id.cover_view) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_PLAYING, 0));
setState(ContextApplication.getService().toggleFlag(PlaybackService.FLAG_PLAYING));
return true;
}
@ -361,18 +376,6 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
* called on the UI Handler.
*/
private static final int MSG_UPDATE_PROGRESS = 10;
/**
* Update the metadata for the current song.
*/
private static final int MSG_UPDATE_SONG = 11;
/**
* The the no media overlay message. This must be called on the UI Handler.
*/
private static final int MSG_SHOW_NO_MEDIA = 12;
/**
* Hide any overlay messages. This must be called on the UI Handler.
*/
private static final int MSG_HIDE_MESSAGE_OVERLAY = 13;
/**
* Save the hidden_controls preference to storage.
*/
@ -392,33 +395,6 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
case MSG_UPDATE_PROGRESS:
updateProgress();
break;
case MSG_UPDATE_SONG: {
Song song = (Song)message.obj;
if (song == null) {
if (mTitle != null) {
mTitle.setText(null);
mAlbum.setText(null);
mArtist.setText(null);
}
mDuration = 0;
} else {
if (mTitle != null) {
mTitle.setText(song.title);
mAlbum.setText(song.album);
mArtist.setText(song.artist);
}
mDuration = song.duration;
}
updateProgress();
break;
}
case MSG_SHOW_NO_MEDIA:
showMessageOverlay();
setNoMediaOverlayMessage();
break;
case MSG_HIDE_MESSAGE_OVERLAY:
hideMessageOverlay();
break;
default:
return super.handleMessage(message);
}

View File

@ -38,11 +38,11 @@ public class MiniPlaybackActivity extends PlaybackActivity implements View.OnCli
{
super.onCreate(state);
requestWindowFeature(Window.FEATURE_NO_TITLE);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mini_playback);
mCoverView = (CoverView)findViewById(R.id.cover_view);
mCoverView.setupHandler(mLooper);
mCoverView.setup(mLooper, this, CoverBitmap.STYLE_OVERLAPPING_BOX);
View openButton = findViewById(R.id.open_button);
openButton.setOnClickListener(this);

View File

@ -73,7 +73,11 @@ public class OneCellWidget extends AppWidgetProvider {
String action = intent.getAction();
if (PlaybackService.EVENT_CHANGED.equals(action) || PlaybackService.EVENT_REPLACE_SONG.equals(action)) {
Context context = ContextApplication.getContext();
Song song = intent.getParcelableExtra("song");
Song song;
if (intent.hasExtra("song"))
song = intent.getParcelableExtra("song");
else
song = ContextApplication.getService().getSong(0);
int state = intent.getIntExtra("state", -1);
AppWidgetManager manager = AppWidgetManager.getInstance(context);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -29,12 +29,13 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
public class PlaybackActivity extends Activity implements Handler.Callback, View.OnClickListener {
public class PlaybackActivity extends Activity implements Handler.Callback, View.OnClickListener, CoverView.Callback {
Handler mHandler;
Looper mLooper;
@ -42,6 +43,9 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
ControlButton mPlayPauseButton;
int mState;
private long mLastStateEvent;
private long mLastSongEvent;
@Override
public void onCreate(Bundle state)
{
@ -130,21 +134,27 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
return handleKeyLongPress(keyCode);
}
public void nextSong()
{
onSongChange(ContextApplication.getService().nextSong());
}
public void previousSong()
{
onSongChange(ContextApplication.getService().previousSong());
}
public void onClick(View view)
{
switch (view.getId()) {
case R.id.next:
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_SONG, 1, 0));
if (mCoverView != null)
mCoverView.go(1);
nextSong();
break;
case R.id.play_pause:
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_PLAYING, 0));
setState(ContextApplication.getService().toggleFlag(PlaybackService.FLAG_PLAYING));
break;
case R.id.previous:
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_SONG, -1, 0));
if (mCoverView != null)
mCoverView.go(-1);
previousSong();
break;
}
}
@ -157,13 +167,15 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
*/
protected void setState(int state)
{
mLastStateEvent = SystemClock.uptimeMillis();
if (mState == state)
return;
mState = state;
int toggled = mState ^ state;
if (mPlayPauseButton != null) {
final int res = (mState & PlaybackService.FLAG_PLAYING) == 0 ? R.drawable.play : R.drawable.pause;
if ((toggled & PlaybackService.FLAG_PLAYING) != 0 && mPlayPauseButton != null) {
final int res = (state & PlaybackService.FLAG_PLAYING) == 0 ? R.drawable.play : R.drawable.pause;
runOnUiThread(new Runnable() {
public void run()
{
@ -171,6 +183,8 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
}
});
}
mState = state;
}
/**
@ -179,9 +193,25 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
*/
protected void onServiceReady()
{
if (mCoverView != null)
mCoverView.initialize();
setState(ContextApplication.getService().getState());
PlaybackService service = ContextApplication.getService();
onSongChange(service.getSong(0));
setState(service.getState());
}
/**
* Called when the current song changes.
*/
protected void onSongChange(final Song song)
{
mLastSongEvent = SystemClock.uptimeMillis();
if (mCoverView != null) {
runOnUiThread(new Runnable() {
public void run()
{
mCoverView.setCurrentSong(song);
}
});
}
}
/**
@ -193,12 +223,21 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
{
String action = intent.getAction();
if (PlaybackService.EVENT_INITIALIZED.equals(action))
onServiceReady();
if (PlaybackService.EVENT_CHANGED.equals(action)) {
long time = intent.getLongExtra("time", -1);
assert(time != -1);
int state = intent.getIntExtra("state", -1);
if (state != -1 && time > mLastStateEvent)
setState(state);
if (intent.hasExtra("song") && time > mLastSongEvent) {
Song song = intent.getParcelableExtra("song");
onSongChange(song);
}
}
if (mCoverView != null)
mCoverView.receive(intent);
if (PlaybackService.EVENT_CHANGED.equals(action))
setState(intent.getIntExtra("state", 0));
}
/**
@ -245,10 +284,10 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
ContextApplication.quit();
return true;
case MENU_SHUFFLE:
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_SHUFFLE, 0));
setState(ContextApplication.getService().toggleFlag(PlaybackService.FLAG_SHUFFLE));
return true;
case MENU_REPEAT:
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_REPEAT, 0));
setState(ContextApplication.getService().toggleFlag(PlaybackService.FLAG_REPEAT));
return true;
case MENU_PREFS:
startActivity(new Intent(this, PreferencesActivity.class));
@ -258,35 +297,9 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
}
}
/**
* Tell PlaybackService to toggle a state flag (passed as arg1) and display
* a message notifying that the flag was toggled.
*/
static final int MSG_TOGGLE_FLAG = 0;
/**
* Tell PlaybackService to change the current song.
*
* arg1 should be the delta, -1 or 1, indicating the previous or next song,
* respectively.
*/
static final int MSG_SET_SONG = 1;
public boolean handleMessage(Message message)
@Override
public boolean handleMessage(Message msg)
{
switch (message.what) {
case MSG_TOGGLE_FLAG:
ContextApplication.getService().toggleFlag(message.arg1);
break;
case MSG_SET_SONG:
if (message.arg1 == 1)
ContextApplication.getService().nextSong();
else
ContextApplication.getService().previousSong();
break;
default:
return false;
}
return true;
return false;
}
}

View File

@ -137,7 +137,6 @@ public final class PlaybackService extends Service implements Handler.Callback,
public static final String EVENT_REPLACE_SONG = "org.kreed.vanilla.event.REPLACE_SONG";
public static final String EVENT_CHANGED = "org.kreed.vanilla.event.CHANGED";
public static final String EVENT_INITIALIZED = "org.kreed.vanilla.event.INITIALIZED";
public static final int FLAG_NO_MEDIA = 0x2;
public static final int FLAG_PLAYING = 0x1;
@ -391,8 +390,6 @@ public final class PlaybackService extends Service implements Handler.Callback,
private void initialize()
{
ContextApplication.broadcast(new Intent(EVENT_INITIALIZED));
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
@ -433,10 +430,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
mHeadsetPause = settings.getBoolean("headset_pause", true);
} else if ("remote_player".equals(key)) {
// the preference is loaded in SongNotification class
updateNotification(getSong(0));
updateNotification();
} else if ("notification_mode".equals(key)){
mNotificationMode = Integer.parseInt(settings.getString("notification_mode", "1"));
updateNotification(getSong(0));
updateNotification();
} else if ("scrobble".equals(key)) {
mScrobble = settings.getBoolean("scrobble", false);
} else if ("volume".equals(key)) {
@ -474,14 +471,14 @@ public final class PlaybackService extends Service implements Handler.Callback,
private void setFlag(int flag)
{
synchronized (mStateLock) {
updateState(mState | flag, false);
updateState(mState | flag);
}
}
private void unsetFlag(int flag)
{
synchronized (mStateLock) {
updateState(mState & ~flag, false);
updateState(mState & ~flag);
}
}
@ -489,10 +486,9 @@ public final class PlaybackService extends Service implements Handler.Callback,
* Modify the service state.
*
* @param state Union of PlaybackService.STATE_* flags
* @param forceBroadcast Broadcast state/song update even if the state has
* not changed.
* @return The new state
*/
private void updateState(int state, boolean forceBroadcast)
private int updateState(int state)
{
state &= ALL_FLAGS;
@ -508,33 +504,44 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
if (song == null && (state & FLAG_PLAYING) != 0)
return;
return state;
if (song == null)
state &= ~FLAG_REPEAT;
int oldState = mState;
mState = state;
if (state != oldState || forceBroadcast) {
Intent intent = new Intent(EVENT_CHANGED);
intent.putExtra("state", state);
intent.putExtra("song", song);
intent.putExtra("pos", mTimeline.getCurrentPosition());
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST, intent));
if (mScrobble) {
intent = new Intent("net.jjc1138.android.scrobbler.action.MUSIC_STATUS");
intent.putExtra("playing", (state & FLAG_PLAYING) != 0);
if (song != null)
intent.putExtra("id", (int)song.id);
sendBroadcast(intent);
}
updateNotification(song);
if (state != oldState) {
mHandler.sendMessage(mHandler.obtainMessage(PROCESS_STATE, oldState, state));
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_CHANGE, state, 0));
}
return state;
}
private void processNewState(int oldState, int state)
{
int toggled = oldState ^ state;
if ((toggled & FLAG_PLAYING) != 0) {
if ((state & FLAG_PLAYING) != 0) {
if (mMediaPlayerInitialized) {
synchronized (mMediaPlayer) {
mMediaPlayer.start();
}
}
if (mNotificationMode != NEVER)
startForegroundCompat(NOTIFICATION_ID, mNotification);
} else {
if (mMediaPlayerInitialized) {
synchronized (mMediaPlayer) {
mMediaPlayer.pause();
}
}
stopForegroundCompat(false);
}
}
if ((toggled & FLAG_SHUFFLE) != 0) {
if ((state & FLAG_SHUFFLE) != 0) {
mTimeline.setShuffle(true);
@ -554,30 +561,42 @@ public final class PlaybackService extends Service implements Handler.Callback,
Toast.makeText(this, R.string.repeat_disabling, Toast.LENGTH_SHORT).show();
}
}
if ((toggled & FLAG_PLAYING) != 0) {
if ((state & FLAG_PLAYING) != 0) {
if (mNotificationMode != NEVER)
startForegroundCompat(NOTIFICATION_ID, mNotification);
if (mMediaPlayerInitialized) {
synchronized (mMediaPlayer) {
mMediaPlayer.start();
}
}
} else {
stopForegroundCompat(false);
if (mMediaPlayerInitialized) {
synchronized (mMediaPlayer) {
mMediaPlayer.pause();
}
}
}
}
}
private void updateNotification(Song song)
private void broadcastChange(int state, Song song, long uptime)
{
updateNotification();
Intent intent = new Intent(EVENT_CHANGED);
if (state != -1)
intent.putExtra("state", state);
if (song != null)
intent.putExtra("song", song);
intent.putExtra("time", uptime);
ContextApplication.broadcast(intent);
if (mScrobble)
scrobble();
}
private void scrobble()
{
assert(mScrobble == true);
Song song = getSong(0);
int state = mState;
Intent intent = new Intent("net.jjc1138.android.scrobbler.action.MUSIC_STATUS");
intent.putExtra("playing", (state & FLAG_PLAYING) != 0);
if (song != null)
intent.putExtra("id", (int)song.id);
sendBroadcast(intent);
}
private void updateNotification()
{
boolean shouldNotify = mNotificationMode == ALWAYS || mNotificationMode == WHEN_PLAYING && (mState & FLAG_PLAYING) != 0;
Song song = getSong(0);
if (song != null && shouldNotify) {
mNotification = new SongNotification(song, (mState & FLAG_PLAYING) != 0);
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
@ -590,38 +609,56 @@ public final class PlaybackService extends Service implements Handler.Callback,
* Toggle a flag in the state on or off
*
* @param flag The flag to be toggled
* @return The new state
*/
public void toggleFlag(int flag)
public int toggleFlag(int flag)
{
int state;
synchronized (mStateLock) {
updateState(mState ^ flag, false);
state = updateState(mState ^ flag);
}
userActionTriggered();
return state;
}
/**
* Move <code>delta</code> places away from the current song.
*
* @return The new current song
*/
private void setCurrentSong(int delta)
private Song setCurrentSong(int delta)
{
if (mMediaPlayer == null)
return;
return null;
synchronized (mMediaPlayer) {
mMediaPlayer.stop();
if (mMediaPlayer.isPlaying())
mMediaPlayer.stop();
}
Song song = mTimeline.shiftCurrentSong(delta);
if (song == null) {
if (Song.isSongAvailable())
setCurrentSong(+1); // we only encountered a bad song; skip it
else
if (Song.isSongAvailable()) {
return setCurrentSong(+1); // we only encountered a bad song; skip it
} else {
setFlag(FLAG_NO_MEDIA); // we don't have any songs : /
return;
return null;
}
} else if ((mState & FLAG_NO_MEDIA) != 0) {
unsetFlag(FLAG_NO_MEDIA);
}
mHandler.removeMessages(PROCESS_SONG);
mHandler.sendMessage(mHandler.obtainMessage(PROCESS_SONG, song));
Message msg = mHandler.obtainMessage(BROADCAST_CHANGE, -1, 0);
msg.obj = song;
mHandler.sendMessage(msg);
return song;
}
private void processSong(Song song)
{
try {
synchronized (mMediaPlayer) {
mMediaPlayer.reset();
@ -632,15 +669,18 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
if ((mState & FLAG_PLAYING) != 0)
mMediaPlayer.start();
updateState(mState & ~FLAG_ERROR, true);
updateState(mState & ~FLAG_ERROR);
} catch (IOException e) {
// FLAG_PLAYING is set to force showing the Toast. updateState()
// will unset it.
updateState(mState | FLAG_PLAYING | FLAG_ERROR, true);
updateState(mState | FLAG_PLAYING | FLAG_ERROR);
Log.e("VanillaMusic", "IOException", e);
}
mHandler.sendEmptyMessage(PROCESS_SONG);
getSong(+2);
mTimeline.purge();
mHandler.removeMessages(SAVE_STATE);
mHandler.sendEmptyMessageDelayed(SAVE_STATE, 5000);
}
public void onCompletion(MediaPlayer player)
@ -787,8 +827,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
* @see ContextApplication#broadcast(Intent)
*/
private static final int BROADCAST = 9;
private static final int BROADCAST_CHANGE = 10;
private static final int SAVE_STATE = 12;
private static final int PROCESS_SONG = 13;
private static final int PROCESS_STATE = 14;
public boolean handleMessage(Message message)
{
@ -817,10 +859,7 @@ public final class PlaybackService extends Service implements Handler.Callback,
mTimeline.saveState(this, 0);
break;
case PROCESS_SONG:
getSong(+2);
mTimeline.purge();
mHandler.removeMessages(SAVE_STATE);
mHandler.sendEmptyMessageDelayed(SAVE_STATE, 5000);
processSong((Song)message.obj);
break;
case CREATE:
initialize();
@ -854,9 +893,15 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
}
break;
case PROCESS_STATE:
processNewState(message.arg1, message.arg2);
break;
case BROADCAST:
ContextApplication.broadcast((Intent)message.obj);
break;
case BROADCAST_CHANGE:
broadcastChange(message.arg1, (Song)message.obj, message.getWhen());
break;
default:
return false;
}
@ -943,19 +988,21 @@ public final class PlaybackService extends Service implements Handler.Callback,
/**
* Move to the next song in the queue.
*/
public void nextSong()
public Song nextSong()
{
setCurrentSong(+1);
Song song = setCurrentSong(+1);
userActionTriggered();
return song;
}
/**
* Move to the previous song in the queue.
*/
public void previousSong()
public Song previousSong()
{
setCurrentSong(-1);
Song song = setCurrentSong(-1);
userActionTriggered();
return song;
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -192,15 +192,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
out.putSerializable("limiter_genres", mGenreAdapter.getLimiter());
}
@Override
protected void onServiceReady()
{
super.onServiceReady();
if (mStatusText != null)
onSongChange(ContextApplication.getService().getSong(0));
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
@ -677,12 +668,14 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
private static int mCoverSize = -1;
/**
* Call to update the status text for a newly-playing song. Should only be
* called after the controls have been initialized.
*/
private void onSongChange(final Song song)
@Override
protected void onSongChange(final Song song)
{
super.onSongChange(song);
if (mStatusText == null)
return;
Resources res = getResources();
CharSequence text;
if (song == null) {
@ -709,13 +702,4 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
}
});
}
@Override
public void receive(Intent intent)
{
super.receive(intent);
if (mControls != null && PlaybackService.EVENT_CHANGED.equals(intent.getAction()))
onSongChange((Song)intent.getParcelableExtra("song"));
}
}