2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.music;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.SearchManager;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29 import android.graphics.Bitmap;
30 import android.media.AudioManager;
31 import android.media.MediaFile;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.RemoteException;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.SystemClock;
40 import android.provider.MediaStore;
41 import android.text.Layout;
42 import android.text.TextUtils.TruncateAt;
43 import android.util.Log;
44 import android.view.KeyEvent;
45 import android.view.Menu;
46 import android.view.MenuItem;
47 import android.view.MotionEvent;
48 import android.view.SubMenu;
49 import android.view.View;
50 import android.view.ViewConfiguration;
51 import android.view.Window;
52 import android.widget.ImageButton;
53 import android.widget.ImageView;
54 import android.widget.ProgressBar;
55 import android.widget.SeekBar;
56 import android.widget.TextView;
57 import android.widget.Toast;
58 import android.widget.SeekBar.OnSeekBarChangeListener;
61 public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs,
62 View.OnTouchListener, View.OnLongClickListener
64 private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
66 private boolean mOneShot = false;
67 private boolean mSeeking = false;
68 private boolean mTrackball;
69 private long mStartSeekPos = 0;
70 private long mLastSeekEventTime;
71 private IMediaPlaybackService mService = null;
72 private RepeatingImageButton mPrevButton;
73 private ImageButton mPauseButton;
74 private RepeatingImageButton mNextButton;
75 private ImageButton mRepeatButton;
76 private ImageButton mShuffleButton;
77 private ImageButton mQueueButton;
78 private Worker mAlbumArtWorker;
79 private AlbumArtHandler mAlbumArtHandler;
81 private boolean mRelaunchAfterConfigChange;
82 private int mTouchSlop;
84 public MediaPlaybackActivity()
88 /** Called when the activity is first created. */
90 public void onCreate(Bundle icicle)
92 super.onCreate(icicle);
93 setVolumeControlStream(AudioManager.STREAM_MUSIC);
95 mAlbumArtWorker = new Worker("album art worker");
96 mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
98 requestWindowFeature(Window.FEATURE_NO_TITLE);
99 setContentView(R.layout.audio_player);
101 mCurrentTime = (TextView) findViewById(R.id.currenttime);
102 mTotalTime = (TextView) findViewById(R.id.totaltime);
103 mProgress = (ProgressBar) findViewById(android.R.id.progress);
104 mAlbum = (ImageView) findViewById(R.id.album);
105 mArtistName = (TextView) findViewById(R.id.artistname);
106 mAlbumName = (TextView) findViewById(R.id.albumname);
107 mTrackName = (TextView) findViewById(R.id.trackname);
109 View v = (View)mArtistName.getParent();
110 v.setOnTouchListener(this);
111 v.setOnLongClickListener(this);
113 v = (View)mAlbumName.getParent();
114 v.setOnTouchListener(this);
115 v.setOnLongClickListener(this);
117 v = (View)mTrackName.getParent();
118 v.setOnTouchListener(this);
119 v.setOnLongClickListener(this);
121 mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
122 mPrevButton.setOnClickListener(mPrevListener);
123 mPrevButton.setRepeatListener(mRewListener, 260);
124 mPauseButton = (ImageButton) findViewById(R.id.pause);
125 mPauseButton.requestFocus();
126 mPauseButton.setOnClickListener(mPauseListener);
127 mNextButton = (RepeatingImageButton) findViewById(R.id.next);
128 mNextButton.setOnClickListener(mNextListener);
129 mNextButton.setRepeatListener(mFfwdListener, 260);
132 mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation ==
133 Resources.Configuration.NAVIGATION_TRACKBALL);*/
135 mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
136 mQueueButton.setOnClickListener(mQueueListener);
137 mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
138 mShuffleButton.setOnClickListener(mShuffleListener);
139 mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
140 mRepeatButton.setOnClickListener(mRepeatListener);
142 if (mProgress instanceof SeekBar) {
143 SeekBar seeker = (SeekBar) mProgress;
144 seeker.setOnSeekBarChangeListener(mSeekListener);
146 mProgress.setMax(1000);
148 if (icicle != null) {
149 mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
150 mOneShot = icicle.getBoolean("oneshot");
152 mOneShot = getIntent().getBooleanExtra("oneshot", false);
155 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
162 boolean mDraggingLabel = false;
164 TextView textViewForContainer(View v) {
165 View vv = v.findViewById(R.id.artistname);
166 if (vv != null) return (TextView) vv;
167 vv = v.findViewById(R.id.albumname);
168 if (vv != null) return (TextView) vv;
169 vv = v.findViewById(R.id.trackname);
170 if (vv != null) return (TextView) vv;
174 public boolean onTouch(View v, MotionEvent event) {
175 int action = event.getAction();
176 TextView tv = textViewForContainer(v);
180 if (action == MotionEvent.ACTION_DOWN) {
181 v.setBackgroundColor(0xff606060);
182 mInitialX = mLastX = (int) event.getX();
183 mDraggingLabel = false;
184 } else if (action == MotionEvent.ACTION_UP ||
185 action == MotionEvent.ACTION_CANCEL) {
186 v.setBackgroundColor(0);
187 if (mDraggingLabel) {
188 Message msg = mLabelScroller.obtainMessage(0, tv);
189 mLabelScroller.sendMessageDelayed(msg, 1000);
191 } else if (action == MotionEvent.ACTION_MOVE) {
192 if (mDraggingLabel) {
193 int scrollx = tv.getScrollX();
194 int x = (int) event.getX();
195 int delta = mLastX - x;
199 if (scrollx > mTextWidth) {
200 // scrolled the text completely off the view to the left
201 scrollx -= mTextWidth;
202 scrollx -= mViewWidth;
204 if (scrollx < -mViewWidth) {
205 // scrolled the text completely off the view to the right
206 scrollx += mViewWidth;
207 scrollx += mTextWidth;
209 tv.scrollTo(scrollx, 0);
213 int delta = mInitialX - (int) event.getX();
214 if (Math.abs(delta) > mTouchSlop) {
216 mLabelScroller.removeMessages(0, tv);
218 // Only turn ellipsizing off when it's not already off, because it
219 // causes the scroll position to be reset to 0.
220 if (tv.getEllipsize() != null) {
221 tv.setEllipsize(null);
223 Layout ll = tv.getLayout();
224 // layout might be null if the text just changed, or ellipsizing
225 // was just turned off
229 // get the non-ellipsized line width, to determine whether scrolling
230 // should even be allowed
231 mTextWidth = (int) tv.getLayout().getLineWidth(0);
232 mViewWidth = tv.getWidth();
233 if (mViewWidth > mTextWidth) {
234 tv.setEllipsize(TruncateAt.END);
238 mDraggingLabel = true;
239 tv.setHorizontalFadingEdgeEnabled(true);
247 Handler mLabelScroller = new Handler() {
249 public void handleMessage(Message msg) {
250 TextView tv = (TextView) msg.obj;
251 int x = tv.getScrollX();
255 tv.setEllipsize(TruncateAt.END);
257 Message newmsg = obtainMessage(0, tv);
258 mLabelScroller.sendMessageDelayed(newmsg, 15);
263 public boolean onLongClick(View view) {
265 CharSequence title = null;
273 artist = mService.getArtistName();
274 album = mService.getAlbumName();
275 song = mService.getTrackName();
276 } catch (RemoteException ex) {
280 boolean knownartist = !MediaFile.UNKNOWN_STRING.equals(artist);
281 boolean knownalbum = !MediaFile.UNKNOWN_STRING.equals(album);
283 if (knownartist && view.equals(mArtistName.getParent())) {
285 query = artist.toString();
286 mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
287 } else if (knownalbum && view.equals(mAlbumName.getParent())) {
290 query = artist + " " + album;
294 mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
295 } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
298 query = artist + " " + song;
302 mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
304 throw new RuntimeException("shouldn't be here");
306 title = getString(R.string.mediasearch, title);
308 Intent i = new Intent();
309 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
310 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
311 i.putExtra(SearchManager.QUERY, query);
313 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
316 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
318 i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
319 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
321 startActivity(Intent.createChooser(i, title));
325 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
326 public void onStartTrackingTouch(SeekBar bar) {
327 mLastSeekEventTime = 0;
330 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
331 if (!fromuser || (mService == null)) return;
332 long now = SystemClock.elapsedRealtime();
333 if ((now - mLastSeekEventTime) > 250) {
334 mLastSeekEventTime = now;
335 mPosOverride = mDuration * progress / 1000;
337 mService.seek(mPosOverride);
338 } catch (RemoteException ex) {
341 // trackball event, allow progress updates
348 public void onStopTrackingTouch(SeekBar bar) {
354 private View.OnClickListener mQueueListener = new View.OnClickListener() {
355 public void onClick(View v) {
357 new Intent(Intent.ACTION_EDIT)
358 .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
359 .putExtra("playlist", "nowplaying")
364 private View.OnClickListener mShuffleListener = new View.OnClickListener() {
365 public void onClick(View v) {
370 private View.OnClickListener mRepeatListener = new View.OnClickListener() {
371 public void onClick(View v) {
376 private View.OnClickListener mPauseListener = new View.OnClickListener() {
377 public void onClick(View v) {
382 private View.OnClickListener mPrevListener = new View.OnClickListener() {
383 public void onClick(View v) {
384 if (mService == null) return;
386 if (mService.position() < 2000) {
392 } catch (RemoteException ex) {
397 private View.OnClickListener mNextListener = new View.OnClickListener() {
398 public void onClick(View v) {
399 if (mService == null) return;
402 } catch (RemoteException ex) {
407 private RepeatingImageButton.RepeatListener mRewListener =
408 new RepeatingImageButton.RepeatListener() {
409 public void onRepeat(View v, long howlong, int repcnt) {
410 scanBackward(repcnt, howlong);
414 private RepeatingImageButton.RepeatListener mFfwdListener =
415 new RepeatingImageButton.RepeatListener() {
416 public void onRepeat(View v, long howlong, int repcnt) {
417 scanForward(repcnt, howlong);
422 public void onStop() {
424 if (mService != null && mOneShot && getChangingConfigurations() == 0) {
427 } catch (RemoteException ex) {
430 mHandler.removeMessages(REFRESH);
431 unregisterReceiver(mStatusListener);
432 MusicUtils.unbindFromService(this);
437 public void onSaveInstanceState(Bundle outState) {
438 outState.putBoolean("configchange", getChangingConfigurations() != 0);
439 outState.putBoolean("oneshot", mOneShot);
440 super.onSaveInstanceState(outState);
444 public void onStart() {
448 if (false == MusicUtils.bindToService(this, osc)) {
449 // something went wrong
450 mHandler.sendEmptyMessage(QUIT);
453 IntentFilter f = new IntentFilter();
454 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
455 f.addAction(MediaPlaybackService.META_CHANGED);
456 f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
457 registerReceiver(mStatusListener, new IntentFilter(f));
459 long next = refreshNow();
460 queueNextRefresh(next);
464 public void onNewIntent(Intent intent) {
466 mOneShot = intent.getBooleanExtra("oneshot", false);
470 public void onResume() {
473 setPauseButtonImage();
477 public void onDestroy()
479 mAlbumArtWorker.quit();
481 //System.out.println("***************** playback activity onDestroy\n");
485 public boolean onCreateOptionsMenu(Menu menu) {
486 super.onCreateOptionsMenu(menu);
487 // Don't show the menu items if we got launched by path/filedescriptor, since
488 // those tend to not be in the media database.
489 if (MusicUtils.getCurrentAudioId() >= 0) {
491 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
492 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
494 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
495 R.string.add_to_playlist).setIcon(android.R.drawable.ic_menu_add);
496 MusicUtils.makePlaylistMenu(this, sub);
497 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
498 menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
504 public boolean onPrepareOptionsMenu(Menu menu) {
505 MenuItem item = menu.findItem(PARTY_SHUFFLE);
507 int shuffle = MusicUtils.getCurrentShuffleMode();
508 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
509 item.setIcon(R.drawable.ic_menu_party_shuffle);
510 item.setTitle(R.string.party_shuffle_off);
512 item.setIcon(R.drawable.ic_menu_party_shuffle);
513 item.setTitle(R.string.party_shuffle);
520 public boolean onOptionsItemSelected(MenuItem item) {
523 switch (item.getItemId()) {
525 intent = new Intent();
526 intent.setClass(this, MusicBrowserActivity.class);
527 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
528 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
529 startActivity(intent);
531 case USE_AS_RINGTONE: {
532 // Set the system setting to make this the current ringtone
533 if (mService != null) {
534 MusicUtils.setRingtone(this, mService.getAudioId());
539 if (mService != null) {
540 int shuffle = mService.getShuffleMode();
541 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
542 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
544 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
547 setShuffleButtonImage();
551 intent = new Intent();
552 intent.setClass(this, CreatePlaylist.class);
553 startActivityForResult(intent, NEW_PLAYLIST);
557 case PLAYLIST_SELECTED: {
558 int [] list = new int[1];
559 list[0] = MusicUtils.getCurrentAudioId();
560 int playlist = item.getIntent().getIntExtra("playlist", 0);
561 MusicUtils.addToPlaylist(this, list, playlist);
566 if (mService != null) {
567 int [] list = new int[1];
568 list[0] = MusicUtils.getCurrentAudioId();
569 Bundle b = new Bundle();
570 b.putString("description", getString(R.string.delete_song_desc,
571 mService.getTrackName()));
572 b.putIntArray("items", list);
573 intent = new Intent();
574 intent.setClass(this, DeleteItems.class);
576 startActivityForResult(intent, -1);
581 } catch (RemoteException ex) {
583 return super.onOptionsItemSelected(item);
587 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
588 if (resultCode != RESULT_OK) {
591 switch (requestCode) {
593 Uri uri = intent.getData();
595 int [] list = new int[1];
596 list[0] = MusicUtils.getCurrentAudioId();
597 int playlist = Integer.parseInt(uri.getLastPathSegment());
598 MusicUtils.addToPlaylist(this, list, playlist);
603 private final int keyboard[][] = {
626 KeyEvent.KEYCODE_DEL,
636 KeyEvent.KEYCODE_COMMA,
637 KeyEvent.KEYCODE_PERIOD,
638 KeyEvent.KEYCODE_ENTER
646 private boolean seekMethod1(int keyCode)
648 for(int x=0;x<10;x++) {
649 for(int y=0;y<3;y++) {
650 if(keyboard[y][x] == keyCode) {
653 if(x == lastX && y == lastY) dir = 0;
654 else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
655 else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
657 else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
658 else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
660 else if (y < lastY && x <= 4) dir = 1;
661 else if (y < lastY && x >= 5) dir = -1;
663 else if (y > lastY && x <= 4) dir = -1;
664 else if (y > lastY && x >= 5) dir = 1;
668 mService.seek(mService.position() + dir * 5);
669 } catch (RemoteException ex) {
681 private boolean seekMethod2(int keyCode)
683 if (mService == null) return false;
684 for(int i=0;i<10;i++) {
685 if(keyboard[0][i] == keyCode) {
686 int seekpercentage = 100*i/10;
688 mService.seek(mService.duration() * seekpercentage / 100);
689 } catch (RemoteException ex) {
699 public boolean onKeyUp(int keyCode, KeyEvent event) {
703 case KeyEvent.KEYCODE_DPAD_LEFT:
707 if (mService != null) {
708 if (!mSeeking && mStartSeekPos >= 0) {
709 mPauseButton.requestFocus();
710 if (mStartSeekPos < 1000) {
716 scanBackward(-1, event.getEventTime() - event.getDownTime());
717 mPauseButton.requestFocus();
724 case KeyEvent.KEYCODE_DPAD_RIGHT:
728 if (mService != null) {
729 if (!mSeeking && mStartSeekPos >= 0) {
730 mPauseButton.requestFocus();
733 scanForward(-1, event.getEventTime() - event.getDownTime());
734 mPauseButton.requestFocus();
742 } catch (RemoteException ex) {
744 return super.onKeyUp(keyCode, event);
748 public boolean onKeyDown(int keyCode, KeyEvent event)
751 int repcnt = event.getRepeatCount();
753 if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
760 case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
761 case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
763 case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
764 case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
765 case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
766 case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
768 case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
769 case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
771 case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
772 case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
773 case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
774 case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
778 case KeyEvent.KEYCODE_SLASH:
779 seekmethod = 1 - seekmethod;
782 case KeyEvent.KEYCODE_DPAD_LEFT:
786 if (!mPrevButton.hasFocus()) {
787 mPrevButton.requestFocus();
789 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
791 case KeyEvent.KEYCODE_DPAD_RIGHT:
795 if (!mNextButton.hasFocus()) {
796 mNextButton.requestFocus();
798 scanForward(repcnt, event.getEventTime() - event.getDownTime());
801 case KeyEvent.KEYCODE_S:
805 case KeyEvent.KEYCODE_DPAD_CENTER:
806 case KeyEvent.KEYCODE_SPACE:
810 return super.onKeyDown(keyCode, event);
813 private void scanBackward(int repcnt, long delta) {
814 if(mService == null) return;
817 mStartSeekPos = mService.position();
818 mLastSeekEventTime = 0;
823 // seek at 10x speed for the first 5 seconds
826 // seek at 40x after that
827 delta = 50000 + (delta - 5000) * 40;
829 long newpos = mStartSeekPos - delta;
831 // move to previous track
833 long duration = mService.duration();
834 mStartSeekPos += duration;
837 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
838 mService.seek(newpos);
839 mLastSeekEventTime = delta;
842 mPosOverride = newpos;
848 } catch (RemoteException ex) {
852 private void scanForward(int repcnt, long delta) {
853 if(mService == null) return;
856 mStartSeekPos = mService.position();
857 mLastSeekEventTime = 0;
862 // seek at 10x speed for the first 5 seconds
865 // seek at 40x after that
866 delta = 50000 + (delta - 5000) * 40;
868 long newpos = mStartSeekPos + delta;
869 long duration = mService.duration();
870 if (newpos >= duration) {
871 // move to next track
873 mStartSeekPos -= duration; // is OK to go negative
876 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
877 mService.seek(newpos);
878 mLastSeekEventTime = delta;
881 mPosOverride = newpos;
887 } catch (RemoteException ex) {
891 private void doPauseResume() {
893 if(mService != null) {
894 if (mService.isPlaying()) {
900 setPauseButtonImage();
902 } catch (RemoteException ex) {
906 private void toggleShuffle() {
907 if (mService == null) {
911 int shuffle = mService.getShuffleMode();
912 if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
913 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
914 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
915 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
916 setRepeatButtonImage();
918 showToast(R.string.shuffle_on_notif);
919 } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
920 shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
921 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
922 showToast(R.string.shuffle_off_notif);
924 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
926 setShuffleButtonImage();
927 } catch (RemoteException ex) {
931 private void cycleRepeat() {
932 if (mService == null) {
936 int mode = mService.getRepeatMode();
937 if (mode == MediaPlaybackService.REPEAT_NONE) {
938 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
939 showToast(R.string.repeat_all_notif);
940 } else if (mode == MediaPlaybackService.REPEAT_ALL) {
941 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
942 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
943 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
944 setShuffleButtonImage();
946 showToast(R.string.repeat_current_notif);
948 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
949 showToast(R.string.repeat_off_notif);
951 setRepeatButtonImage();
952 } catch (RemoteException ex) {
957 private void showToast(int resid) {
958 if (mToast == null) {
959 mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
961 mToast.setText(resid);
965 private void startPlayback() {
969 Intent intent = getIntent();
970 String filename = "";
971 Uri uri = intent.getData();
972 if (uri != null && uri.toString().length() > 0) {
973 // If this is a file:// URI, just use the path directly instead
974 // of going through the open-from-filedescriptor codepath.
975 String scheme = uri.getScheme();
976 if ("file".equals(scheme)) {
977 filename = uri.getPath();
979 filename = uri.toString();
983 if (! mRelaunchAfterConfigChange) {
985 mService.openfile(filename);
988 } catch (Exception ex) {
989 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
994 long next = refreshNow();
995 queueNextRefresh(next);
998 private ServiceConnection osc = new ServiceConnection() {
999 public void onServiceConnected(ComponentName classname, IBinder obj) {
1000 mService = IMediaPlaybackService.Stub.asInterface(obj);
1001 if (MusicUtils.sService == null) {
1002 MusicUtils.sService = mService;
1006 // Assume something is playing when the service says it is,
1007 // but also if the audio ID is valid but the service is paused.
1008 if (mService.getAudioId() >= 0 || mService.isPlaying() ||
1009 mService.getPath() != null) {
1010 // something is playing now, we're done
1011 if (mOneShot || mService.getAudioId() < 0) {
1012 mRepeatButton.setVisibility(View.INVISIBLE);
1013 mShuffleButton.setVisibility(View.INVISIBLE);
1014 mQueueButton.setVisibility(View.INVISIBLE);
1016 mRepeatButton.setVisibility(View.VISIBLE);
1017 mShuffleButton.setVisibility(View.VISIBLE);
1018 mQueueButton.setVisibility(View.VISIBLE);
1019 setRepeatButtonImage();
1020 setShuffleButtonImage();
1022 setPauseButtonImage();
1025 } catch (RemoteException ex) {
1027 // Service is dead or not playing anything. If we got here as part
1028 // of a "play this file" Intent, exit. Otherwise go to the Music
1029 // app start screen.
1030 if (getIntent().getData() == null) {
1031 Intent intent = new Intent(Intent.ACTION_MAIN);
1032 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1033 intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
1034 startActivity(intent);
1038 public void onServiceDisconnected(ComponentName classname) {
1042 private void setRepeatButtonImage() {
1044 switch (mService.getRepeatMode()) {
1045 case MediaPlaybackService.REPEAT_ALL:
1046 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
1048 case MediaPlaybackService.REPEAT_CURRENT:
1049 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
1052 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
1055 } catch (RemoteException ex) {
1059 private void setShuffleButtonImage() {
1061 switch (mService.getShuffleMode()) {
1062 case MediaPlaybackService.SHUFFLE_NONE:
1063 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
1065 case MediaPlaybackService.SHUFFLE_AUTO:
1066 mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
1069 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
1072 } catch (RemoteException ex) {
1076 private void setPauseButtonImage() {
1078 if (mService != null && mService.isPlaying()) {
1079 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
1081 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
1083 } catch (RemoteException ex) {
1087 private ImageView mAlbum;
1088 private TextView mCurrentTime;
1089 private TextView mTotalTime;
1090 private TextView mArtistName;
1091 private TextView mAlbumName;
1092 private TextView mTrackName;
1093 private ProgressBar mProgress;
1094 private long mPosOverride = -1;
1095 private boolean mFromTouch = false;
1096 private long mDuration;
1097 private int seekmethod;
1098 private boolean paused;
1100 private static final int REFRESH = 1;
1101 private static final int QUIT = 2;
1102 private static final int GET_ALBUM_ART = 3;
1103 private static final int ALBUM_ART_DECODED = 4;
1105 private void queueNextRefresh(long delay) {
1107 Message msg = mHandler.obtainMessage(REFRESH);
1108 mHandler.removeMessages(REFRESH);
1109 mHandler.sendMessageDelayed(msg, delay);
1113 private long refreshNow() {
1114 if(mService == null)
1117 long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
1118 long remaining = 1000 - (pos % 1000);
1119 if ((pos >= 0) && (mDuration > 0)) {
1120 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
1122 if (mService.isPlaying()) {
1123 mCurrentTime.setVisibility(View.VISIBLE);
1125 // blink the counter
1126 int vis = mCurrentTime.getVisibility();
1127 mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1131 mProgress.setProgress((int) (1000 * pos / mDuration));
1133 mCurrentTime.setText("--:--");
1134 mProgress.setProgress(1000);
1136 // return the number of milliseconds until the next full second, so
1137 // the counter can be updated at just the right time
1139 } catch (RemoteException ex) {
1144 private final Handler mHandler = new Handler() {
1146 public void handleMessage(Message msg) {
1148 case ALBUM_ART_DECODED:
1149 mAlbum.setImageBitmap((Bitmap)msg.obj);
1150 mAlbum.getDrawable().setDither(true);
1154 long next = refreshNow();
1155 queueNextRefresh(next);
1159 // This can be moved back to onCreate once the bug that prevents
1160 // Dialogs from being started from onCreate/onResume is fixed.
1161 new AlertDialog.Builder(MediaPlaybackActivity.this)
1162 .setTitle(R.string.service_start_error_title)
1163 .setMessage(R.string.service_start_error_msg)
1164 .setPositiveButton(R.string.service_start_error_button,
1165 new DialogInterface.OnClickListener() {
1166 public void onClick(DialogInterface dialog, int whichButton) {
1170 .setCancelable(false)
1180 private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1182 public void onReceive(Context context, Intent intent) {
1183 String action = intent.getAction();
1184 if (action.equals(MediaPlaybackService.META_CHANGED)) {
1185 // redraw the artist/title info and
1186 // set new max for progress bar
1188 setPauseButtonImage();
1189 queueNextRefresh(1);
1190 } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1194 setPauseButtonImage();
1196 } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1197 setPauseButtonImage();
1202 private void updateTrackInfo() {
1203 if (mService == null) {
1207 String path = mService.getPath();
1213 if (mService.getAudioId() < 0 && path.toLowerCase().startsWith("http://")) {
1214 ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
1215 ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
1216 mAlbum.setVisibility(View.GONE);
1217 mTrackName.setText(path);
1218 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1219 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, -1, 0).sendToTarget();
1221 ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
1222 ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
1223 String artistName = mService.getArtistName();
1224 if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1225 artistName = getString(R.string.unknown_artist_name);
1227 mArtistName.setText(artistName);
1228 String albumName = mService.getAlbumName();
1229 int albumid = mService.getAlbumId();
1230 if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
1231 albumName = getString(R.string.unknown_album_name);
1234 mAlbumName.setText(albumName);
1235 mTrackName.setText(mService.getTrackName());
1236 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1237 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, albumid, 0).sendToTarget();
1238 mAlbum.setVisibility(View.VISIBLE);
1240 mDuration = mService.duration();
1241 mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1242 } catch (RemoteException ex) {
1247 public class AlbumArtHandler extends Handler {
1248 private int mAlbumId = -1;
1250 public AlbumArtHandler(Looper looper) {
1253 public void handleMessage(Message msg)
1255 int albumid = msg.arg1;
1256 if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1257 // while decoding the new image, show the default album art
1258 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1259 mHandler.removeMessages(ALBUM_ART_DECODED);
1260 mHandler.sendMessageDelayed(numsg, 300);
1261 Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, albumid);
1263 bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
1267 numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1268 mHandler.removeMessages(ALBUM_ART_DECODED);
1269 mHandler.sendMessage(numsg);
1276 private class Worker implements Runnable {
1277 private final Object mLock = new Object();
1278 private Looper mLooper;
1281 * Creates a worker thread with the given name. The thread
1282 * then runs a {@link android.os.Looper}.
1283 * @param name A name for the new thread
1285 Worker(String name) {
1286 Thread t = new Thread(null, this, name);
1287 t.setPriority(Thread.MIN_PRIORITY);
1289 synchronized (mLock) {
1290 while (mLooper == null) {
1293 } catch (InterruptedException ex) {
1299 public Looper getLooper() {
1304 synchronized (mLock) {
1306 mLooper = Looper.myLooper();
1312 public void quit() {