package org.music.player; import org.music.player.R; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.view.ContextMenu; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.Toast; /** * Base activity for activities that contain playback controls. Handles * communication with the PlaybackService and response to state and song * changes. */ public abstract class PlaybackActivity extends Activity implements Handler.Callback, View.OnClickListener, CoverView.Callback { private Action mUpAction; private Action mDownAction; /** * A Handler running on the UI thread, in contrast with mHandler which runs * on a worker thread. */ protected final Handler mUiHandler = new Handler(this); /** * A Handler running on a worker thread. */ protected Handler mHandler; /** * The looper for the worker thread. */ protected Looper mLooper; protected CoverView mCoverView; protected ImageButton mPlayPauseButton; protected ImageButton mShuffleButton; protected ImageButton mEndButton; protected int mState; private long mLastStateEvent; private long mLastSongEvent; @Override public void onCreate(Bundle state) { super.onCreate(state); PlaybackService.addActivity(this); setVolumeControlStream(AudioManager.STREAM_MUSIC); HandlerThread thread = new HandlerThread(getClass().getName(), Process.THREAD_PRIORITY_LOWEST); thread.start(); mLooper = thread.getLooper(); mHandler = new Handler(mLooper, this); } @Override public void onDestroy() { PlaybackService.removeActivity(this); mLooper.quit(); super.onDestroy(); } @Override public void onStart() { super.onStart(); if (PlaybackService.hasInstance()) onServiceReady(); else startService(new Intent(this, PlaybackService.class)); SharedPreferences prefs = PlaybackService.getSettings(this); mUpAction = Action.getAction(prefs, PrefKeys.SWIPE_UP_ACTION, Action.Nothing); mDownAction = Action.getAction(prefs, PrefKeys.SWIPE_DOWN_ACTION, Action.Nothing); Window window = getWindow(); if (prefs.getBoolean(PrefKeys.DISABLE_LOCKSCREEN, false)) window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); else window.clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); } @Override public void onResume() { super.onResume(); MediaButtonReceiver.registerMediaButton(this); MediaButtonReceiver.setInCall(false); if (PlaybackService.hasInstance()) { PlaybackService service = PlaybackService.get(this); service.userActionTriggered(); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: return MediaButtonReceiver.processKey(this, event); } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: return MediaButtonReceiver.processKey(this, event); } return super.onKeyUp(keyCode, event); } @Override public void shiftCurrentSong(int delta) { setSong(PlaybackService.get(this).shiftCurrentSong(delta)); } public void playPause() { PlaybackService service = PlaybackService.get(this); int state = service.playPause(); if ((state & PlaybackService.FLAG_ERROR) != 0) Toast.makeText(this, service.getErrorMessage(), Toast.LENGTH_LONG).show(); setState(state); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.next: shiftCurrentSong(SongTimeline.SHIFT_NEXT_SONG); break; case R.id.play_pause: playPause(); break; case R.id.previous: shiftCurrentSong(SongTimeline.SHIFT_PREVIOUS_SONG); break; case R.id.end_action: cycleFinishAction(); break; case R.id.shuffle: cycleShuffle(); break; } } /** * Called when the PlaybackService state has changed. * * @param state PlaybackService state * @param toggled The flags that have changed from the previous state */ protected void onStateChange(int state, int toggled) { if ((toggled & PlaybackService.FLAG_PLAYING) != 0 && mPlayPauseButton != null) { mPlayPauseButton.setImageResource((state & PlaybackService.FLAG_PLAYING) == 0 ? R.drawable.play : R.drawable.pause); } if ((toggled & PlaybackService.MASK_FINISH) != 0 && mEndButton != null) { mEndButton.setImageResource(SongTimeline.FINISH_ICONS[PlaybackService.finishAction(state)]); } if ((toggled & PlaybackService.MASK_SHUFFLE) != 0 && mShuffleButton != null) { mShuffleButton.setImageResource(SongTimeline.SHUFFLE_ICONS[PlaybackService.shuffleMode(state)]); } } protected void setState(final int state) { mLastStateEvent = SystemClock.uptimeMillis(); if (mState != state) { final int toggled = mState ^ state; mState = state; runOnUiThread(new Runnable() { @Override public void run() { onStateChange(state, toggled); } }); } } /** * Called by PlaybackService to update the state. */ public void setState(long uptime, int state) { if (uptime > mLastStateEvent) setState(state); } /** * Sets up components when the PlaybackService is initialized and available to * interact with. Override to implement further post-initialization behavior. */ protected void onServiceReady() { PlaybackService service = PlaybackService.get(this); setSong(service.getSong(0)); setState(service.getState()); } /** * Called when the current song changes. * * @param song The new song */ protected void onSongChange(Song song) { if (mCoverView != null) mCoverView.querySongs(PlaybackService.get(this)); } protected void setSong(final Song song) { mLastSongEvent = SystemClock.uptimeMillis(); runOnUiThread(new Runnable() { @Override public void run() { onSongChange(song); } }); } /** * Called by PlaybackService to update the current song. */ public void setSong(long uptime, Song song) { if (uptime > mLastSongEvent) setSong(song); } /** * Called by PlaybackService to update an active song (next, previous, or * current). */ public void replaceSong(int delta, Song song) { if (mCoverView != null) mCoverView.setSong(delta + 1, song); } /** * Called when the song timeline position/size has changed. */ public void onPositionInfoChanged() { } /** * Called when the content of the media store has changed. */ public void onMediaChange() { } static final int MENU_SORT = 1; static final int MENU_PREFS = 2; static final int MENU_LIBRARY = 3; static final int MENU_PLAYBACK = 5; static final int MENU_SEARCH = 7; static final int MENU_ENQUEUE_ALBUM = 8; static final int MENU_ENQUEUE_ARTIST = 9; static final int MENU_ENQUEUE_GENRE = 10; static final int MENU_CLEAR_QUEUE = 11; static final int MENU_TOGGLE_CONTROLS = 12; @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, MENU_PREFS, 0, R.string.settings).setIcon(R.drawable.ic_menu_preferences); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_PREFS: startActivity(new Intent(this, PreferencesActivity.class)); return true; default: return false; } } @Override public boolean handleMessage(Message msg) { return false; } /** * Cycle shuffle mode. */ public void cycleShuffle() { setState(PlaybackService.get(this).cycleShuffle()); } /** * Cycle the finish action. */ public void cycleFinishAction() { setState(PlaybackService.get(this).cycleFinishAction()); } /** * Open the library activity. * * @param song If non-null, will open the library focused on this song. */ public void openLibrary(Song song) { Intent intent = new Intent(this, LibraryActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); if (song != null) { intent.putExtra("albumId", song.albumId); intent.putExtra("album", song.album); intent.putExtra("artist", song.artist); } startActivity(intent); } @Override public void upSwipe() { PlaybackService.get(this).performAction(mUpAction, this); } @Override public void downSwipe() { PlaybackService.get(this).performAction(mDownAction, this); } private static final int GROUP_SHUFFLE = 100; private static final int GROUP_FINISH = 101; @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { if (view == mShuffleButton) { menu.add(GROUP_SHUFFLE, SongTimeline.SHUFFLE_NONE, 0, R.string.no_shuffle); menu.add(GROUP_SHUFFLE, SongTimeline.SHUFFLE_SONGS, 0, R.string.shuffle_songs); menu.add(GROUP_SHUFFLE, SongTimeline.SHUFFLE_ALBUMS, 0, R.string.shuffle_albums); } else if (view == mEndButton) { menu.add(GROUP_FINISH, SongTimeline.FINISH_STOP, 0, R.string.no_repeat); menu.add(GROUP_FINISH, SongTimeline.FINISH_REPEAT, 0, R.string.repeat); menu.add(GROUP_FINISH, SongTimeline.FINISH_REPEAT_CURRENT, 0, R.string.repeat_current_song); menu.add(GROUP_FINISH, SongTimeline.FINISH_STOP_CURRENT, 0, R.string.stop_current_song); menu.add(GROUP_FINISH, SongTimeline.FINISH_RANDOM, 0, R.string.random); } } @Override public boolean onContextItemSelected(MenuItem item) { int group = item.getGroupId(); int id = item.getItemId(); if (group == GROUP_SHUFFLE) setState(PlaybackService.get(this).setShuffleMode(id)); else if (group == GROUP_FINISH) setState(PlaybackService.get(this).setFinishAction(id)); return true; } }