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.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.ServiceConnection;
28 import android.content.pm.ActivityInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.graphics.Bitmap;
32 import android.media.AudioManager;
33 import android.media.MediaFile;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.RemoteException;
37 import android.os.Handler;
38 import android.os.IBinder;
39 import android.os.Looper;
40 import android.os.Message;
41 import android.os.SystemClock;
42 import android.util.Log;
43 import android.view.ContextMenu;
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.Window;
51 import android.view.ContextMenu.ContextMenuInfo;
52 import android.widget.ImageButton;
53 import android.widget.ProgressBar;
54 import android.widget.SeekBar;
55 import android.widget.TextView;
56 import android.widget.Toast;
57 import android.widget.SeekBar.OnSeekBarChangeListener;
60 public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs, View.OnTouchListener
62 private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
64 private boolean mOneShot = false;
65 private boolean mSeeking = false;
66 private boolean mTrackball;
67 private long mStartSeekPos = 0;
68 private long mLastSeekEventTime;
69 private IMediaPlaybackService mService = null;
70 private RepeatingImageButton mPrevButton;
71 private ImageButton mPauseButton;
72 private RepeatingImageButton mNextButton;
73 private ImageButton mRepeatButton;
74 private ImageButton mShuffleButton;
75 private ImageButton mQueueButton;
76 private Worker mAlbumArtWorker;
77 private AlbumArtHandler mAlbumArtHandler;
79 private boolean mRelaunchAfterConfigChange;
81 public MediaPlaybackActivity()
85 /** Called when the activity is first created. */
87 public void onCreate(Bundle icicle)
89 super.onCreate(icicle);
90 setVolumeControlStream(AudioManager.STREAM_MUSIC);
92 mAlbumArtWorker = new Worker("album art worker");
93 mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
95 requestWindowFeature(Window.FEATURE_NO_TITLE);
96 setContentView(R.layout.audio_player);
98 mCurrentTime = (TextView) findViewById(R.id.currenttime);
99 mTotalTime = (TextView) findViewById(R.id.totaltime);
100 mProgress = (ProgressBar) findViewById(android.R.id.progress);
101 mAlbum = (AlbumView) findViewById(R.id.album);
102 mArtistName = (TextView) findViewById(R.id.artistname);
103 mAlbumName = (TextView) findViewById(R.id.albumname);
104 mTrackName = (TextView) findViewById(R.id.trackname);
106 View v = (View)mArtistName.getParent();
107 v.setOnTouchListener(this);
108 registerForContextMenu(v);
110 v = (View)mAlbumName.getParent();
111 v.setOnTouchListener(this);
112 registerForContextMenu(v);
114 v = (View)mTrackName.getParent();
115 v.setOnTouchListener(this);
116 registerForContextMenu(v);
118 mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
119 mPrevButton.setOnClickListener(mPrevListener);
120 mPrevButton.setRepeatListener(mRewListener, 260);
121 mPauseButton = (ImageButton) findViewById(R.id.pause);
122 mPauseButton.requestFocus();
123 mPauseButton.setOnClickListener(mPauseListener);
124 mNextButton = (RepeatingImageButton) findViewById(R.id.next);
125 mNextButton.setOnClickListener(mNextListener);
126 mNextButton.setRepeatListener(mFfwdListener, 260);
129 mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation ==
130 Resources.Configuration.NAVIGATION_TRACKBALL);*/
132 mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
133 mQueueButton.setOnClickListener(mQueueListener);
134 mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
135 mShuffleButton.setOnClickListener(mShuffleListener);
136 mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
137 mRepeatButton.setOnClickListener(mRepeatListener);
139 if (mProgress instanceof SeekBar) {
140 SeekBar seeker = (SeekBar) mProgress;
141 seeker.setOnSeekBarChangeListener(mSeekListener);
143 mProgress.setMax(1000);
145 if (icicle != null) {
146 mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
147 mOneShot = icicle.getBoolean("oneshot");
149 mOneShot = getIntent().getBooleanExtra("oneshot", false);
153 public boolean onTouch(View v, MotionEvent event) {
154 int action = event.getAction();
155 if (action == MotionEvent.ACTION_DOWN) {
156 v.setBackgroundColor(0xff606060);
157 } else if (action == MotionEvent.ACTION_UP ||
158 action == MotionEvent.ACTION_CANCEL) {
159 v.setBackgroundColor(0);
165 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
168 * A better way to do this would be to define a new "media search" intent (which
169 * would behave similar to a regular search intent), and have amazon, youtube, the
170 * browser and other suitable apps support it. Then we could just fire off the
171 * intent and let the user choose from the activity picker.
173 CharSequence title = null;
175 CharSequence artist = mArtistName.getText();
176 CharSequence album = mAlbumName.getText();
177 CharSequence song = mTrackName.getText();
178 if (view.equals(mArtistName.getParent()) && artist.length() > 0) {
180 query = artist.toString();
181 } else if (view.equals(mAlbumName.getParent()) &&
182 artist.length() > 0 && album.length() > 0) {
184 query = artist.toString() + " " + album.toString();
185 } else if (view.equals(mTrackName.getParent()) &&
186 artist.length() > 0 && song.length() > 0) {
188 query = artist.toString() + " " + song.toString();
193 title = getString(R.string.mediasearch, title);
194 TextView tv = new TextView(this);
197 tv.setPadding(8, 8, 8, 8);
198 menu.setHeaderView(tv);
199 //menu.setHeaderTitle(title);
201 Intent i = new Intent();
202 i.setAction(Intent.ACTION_SEARCH);
203 i.setClassName("com.amazon.mp3", "com.amazon.mp3.android.client.SearchActivity");
204 i.putExtra("query", query);
205 PackageManager pm = getPackageManager();
206 ActivityInfo ai = i.resolveActivityInfo(pm, 0);
208 menu.add(R.string.mediasearch_amazon).setIntent(i);
212 i.setAction(Intent.ACTION_WEB_SEARCH);
213 i.putExtra("query", query);
214 menu.add(R.string.mediasearch_google).setIntent(i);
217 i.setAction(Intent.ACTION_SEARCH);
218 i.setClassName("com.google.android.youtube", "com.google.android.youtube.QueryActivity");
219 i.putExtra("query", query);
220 menu.add(R.string.mediasearch_youtube).setIntent(i);
224 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
225 public void onStartTrackingTouch(SeekBar bar) {
226 mLastSeekEventTime = 0;
228 public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) {
229 if (mService == null) return;
231 long now = SystemClock.elapsedRealtime();
232 if ((now - mLastSeekEventTime) > 250) {
233 mLastSeekEventTime = now;
234 mPosOverride = mDuration * progress / 1000;
236 mService.seek(mPosOverride);
237 } catch (RemoteException ex) {
242 public void onStopTrackingTouch(SeekBar bar) {
247 private View.OnClickListener mQueueListener = new View.OnClickListener() {
248 public void onClick(View v) {
250 new Intent(Intent.ACTION_EDIT)
251 .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
252 .putExtra("playlist", "nowplaying")
257 private View.OnClickListener mShuffleListener = new View.OnClickListener() {
258 public void onClick(View v) {
263 private View.OnClickListener mRepeatListener = new View.OnClickListener() {
264 public void onClick(View v) {
269 private View.OnClickListener mPauseListener = new View.OnClickListener() {
270 public void onClick(View v) {
275 private View.OnClickListener mPrevListener = new View.OnClickListener() {
276 public void onClick(View v) {
277 if (mService == null) return;
279 if (mService.position() < 2000) {
285 } catch (RemoteException ex) {
290 private View.OnClickListener mNextListener = new View.OnClickListener() {
291 public void onClick(View v) {
292 if (mService == null) return;
295 } catch (RemoteException ex) {
300 private RepeatingImageButton.RepeatListener mRewListener =
301 new RepeatingImageButton.RepeatListener() {
302 public void onRepeat(View v, long howlong, int repcnt) {
303 scanBackward(repcnt, howlong);
307 private RepeatingImageButton.RepeatListener mFfwdListener =
308 new RepeatingImageButton.RepeatListener() {
309 public void onRepeat(View v, long howlong, int repcnt) {
310 scanForward(repcnt, howlong);
315 public void onStop() {
317 if (mService != null && mOneShot && getChangingConfigurations() == 0) {
320 } catch (RemoteException ex) {
323 mHandler.removeMessages(REFRESH);
324 unregisterReceiver(mStatusListener);
325 MusicUtils.unbindFromService(this);
330 public void onSaveInstanceState(Bundle outState) {
331 outState.putBoolean("configchange", getChangingConfigurations() != 0);
332 outState.putBoolean("oneshot", mOneShot);
333 super.onSaveInstanceState(outState);
337 public void onStart() {
341 if (false == MusicUtils.bindToService(this, osc)) {
342 // something went wrong
343 mHandler.sendEmptyMessage(QUIT);
346 IntentFilter f = new IntentFilter();
347 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
348 f.addAction(MediaPlaybackService.META_CHANGED);
349 f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
350 registerReceiver(mStatusListener, new IntentFilter(f));
352 long next = refreshNow();
353 queueNextRefresh(next);
357 public void onNewIntent(Intent intent) {
359 mOneShot = intent.getBooleanExtra("oneshot", false);
363 public void onResume() {
366 setPauseButtonImage();
370 public void onDestroy()
372 mAlbumArtWorker.quit();
374 //System.out.println("***************** playback activity onDestroy\n");
378 public boolean onCreateOptionsMenu(Menu menu) {
379 super.onCreateOptionsMenu(menu);
380 // Don't show the menu items if we got launched by path/filedescriptor, since
381 // those tend to not be in the media database.
382 if (MusicUtils.getCurrentAudioId() >= 0) {
384 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
385 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
387 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
388 R.string.add_to_playlist).setIcon(R.drawable.ic_menu_add);
389 MusicUtils.makePlaylistMenu(this, sub);
390 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
391 menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
397 public boolean onPrepareOptionsMenu(Menu menu) {
398 MenuItem item = menu.findItem(PARTY_SHUFFLE);
400 int shuffle = MusicUtils.getCurrentShuffleMode();
401 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
402 item.setIcon(R.drawable.ic_menu_party_shuffle);
403 item.setTitle(R.string.party_shuffle_off);
405 item.setIcon(R.drawable.ic_menu_party_shuffle);
406 item.setTitle(R.string.party_shuffle);
413 public boolean onOptionsItemSelected(MenuItem item) {
416 switch (item.getItemId()) {
418 intent = new Intent();
419 intent.setClass(this, MusicBrowserActivity.class);
420 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
421 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
422 startActivity(intent);
424 case USE_AS_RINGTONE: {
425 // Set the system setting to make this the current ringtone
426 if (mService != null) {
427 MusicUtils.setRingtone(this, mService.getAudioId());
432 if (mService != null) {
433 int shuffle = mService.getShuffleMode();
434 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
435 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
437 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
440 setShuffleButtonImage();
444 intent = new Intent();
445 intent.setClass(this, CreatePlaylist.class);
446 startActivityForResult(intent, NEW_PLAYLIST);
450 case PLAYLIST_SELECTED: {
451 int [] list = new int[1];
452 list[0] = MusicUtils.getCurrentAudioId();
453 int playlist = item.getIntent().getIntExtra("playlist", 0);
454 MusicUtils.addToPlaylist(this, list, playlist);
459 if (mService != null) {
460 int [] list = new int[1];
461 list[0] = MusicUtils.getCurrentAudioId();
462 Bundle b = new Bundle();
463 b.putString("description", mService.getTrackName());
464 b.putIntArray("items", list);
465 intent = new Intent();
466 intent.setClass(this, DeleteItems.class);
468 startActivityForResult(intent, -1);
473 } catch (RemoteException ex) {
475 return super.onOptionsItemSelected(item);
479 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
480 if (resultCode != RESULT_OK) {
483 switch (requestCode) {
485 Uri uri = Uri.parse(intent.getAction());
487 int [] list = new int[1];
488 list[0] = MusicUtils.getCurrentAudioId();
489 int playlist = Integer.parseInt(uri.getLastPathSegment());
490 MusicUtils.addToPlaylist(this, list, playlist);
495 private final int keyboard[][] = {
518 KeyEvent.KEYCODE_DEL,
528 KeyEvent.KEYCODE_COMMA,
529 KeyEvent.KEYCODE_PERIOD,
530 KeyEvent.KEYCODE_ENTER
538 private boolean seekMethod1(int keyCode)
540 for(int x=0;x<10;x++) {
541 for(int y=0;y<3;y++) {
542 if(keyboard[y][x] == keyCode) {
545 if(x == lastX && y == lastY) dir = 0;
546 else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
547 else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
549 else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
550 else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
552 else if (y < lastY && x <= 4) dir = 1;
553 else if (y < lastY && x >= 5) dir = -1;
555 else if (y > lastY && x <= 4) dir = -1;
556 else if (y > lastY && x >= 5) dir = 1;
560 mService.seek(mService.position() + dir * 5);
561 } catch (RemoteException ex) {
573 private boolean seekMethod2(int keyCode)
575 if (mService == null) return false;
576 for(int i=0;i<10;i++) {
577 if(keyboard[0][i] == keyCode) {
578 int seekpercentage = 100*i/10;
580 mService.seek(mService.duration() * seekpercentage / 100);
581 } catch (RemoteException ex) {
591 public boolean onKeyUp(int keyCode, KeyEvent event) {
595 case KeyEvent.KEYCODE_DPAD_LEFT:
599 if (mService != null) {
600 if (!mSeeking && mStartSeekPos >= 0) {
601 mPauseButton.requestFocus();
602 if (mStartSeekPos < 1000) {
608 scanBackward(-1, event.getEventTime() - event.getDownTime());
609 mPauseButton.requestFocus();
616 case KeyEvent.KEYCODE_DPAD_RIGHT:
620 if (mService != null) {
621 if (!mSeeking && mStartSeekPos >= 0) {
622 mPauseButton.requestFocus();
625 scanForward(-1, event.getEventTime() - event.getDownTime());
626 mPauseButton.requestFocus();
634 } catch (RemoteException ex) {
636 return super.onKeyUp(keyCode, event);
640 public boolean onKeyDown(int keyCode, KeyEvent event)
643 int repcnt = event.getRepeatCount();
645 if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
652 case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
653 case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
655 case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
656 case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
657 case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
658 case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
660 case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
661 case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
663 case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
664 case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
665 case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
666 case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
670 case KeyEvent.KEYCODE_SLASH:
671 seekmethod = 1 - seekmethod;
674 case KeyEvent.KEYCODE_DPAD_LEFT:
678 if (!mPrevButton.hasFocus()) {
679 mPrevButton.requestFocus();
681 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
683 case KeyEvent.KEYCODE_DPAD_RIGHT:
687 if (!mNextButton.hasFocus()) {
688 mNextButton.requestFocus();
690 scanForward(repcnt, event.getEventTime() - event.getDownTime());
693 case KeyEvent.KEYCODE_S:
697 case KeyEvent.KEYCODE_DPAD_CENTER:
698 case KeyEvent.KEYCODE_SPACE:
702 return super.onKeyDown(keyCode, event);
705 private void scanBackward(int repcnt, long delta) {
706 if(mService == null) return;
709 mStartSeekPos = mService.position();
710 mLastSeekEventTime = 0;
715 // seek at 10x speed for the first 5 seconds
718 // seek at 40x after that
719 delta = 50000 + (delta - 5000) * 40;
721 long newpos = mStartSeekPos - delta;
723 // move to previous track
725 long duration = mService.duration();
726 mStartSeekPos += duration;
729 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
730 mService.seek(newpos);
731 mLastSeekEventTime = delta;
734 mPosOverride = newpos;
740 } catch (RemoteException ex) {
744 private void scanForward(int repcnt, long delta) {
745 if(mService == null) return;
748 mStartSeekPos = mService.position();
749 mLastSeekEventTime = 0;
754 // seek at 10x speed for the first 5 seconds
757 // seek at 40x after that
758 delta = 50000 + (delta - 5000) * 40;
760 long newpos = mStartSeekPos + delta;
761 long duration = mService.duration();
762 if (newpos >= duration) {
763 // move to next track
765 mStartSeekPos -= duration; // is OK to go negative
768 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
769 mService.seek(newpos);
770 mLastSeekEventTime = delta;
773 mPosOverride = newpos;
779 } catch (RemoteException ex) {
783 private void doPauseResume() {
785 if(mService != null) {
786 if (mService.isPlaying()) {
792 setPauseButtonImage();
794 } catch (RemoteException ex) {
798 private void toggleShuffle() {
799 if (mService == null) {
803 int shuffle = mService.getShuffleMode();
804 if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
805 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
806 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
807 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
808 setRepeatButtonImage();
810 showToast(R.string.shuffle_on_notif);
811 } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
812 shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
813 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
814 showToast(R.string.shuffle_off_notif);
816 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
818 setShuffleButtonImage();
819 } catch (RemoteException ex) {
823 private void cycleRepeat() {
824 if (mService == null) {
828 int mode = mService.getRepeatMode();
829 if (mode == MediaPlaybackService.REPEAT_NONE) {
830 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
831 showToast(R.string.repeat_all_notif);
832 } else if (mode == MediaPlaybackService.REPEAT_ALL) {
833 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
834 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
835 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
836 setShuffleButtonImage();
838 showToast(R.string.repeat_current_notif);
840 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
841 showToast(R.string.repeat_off_notif);
843 setRepeatButtonImage();
844 } catch (RemoteException ex) {
849 private void showToast(int resid) {
850 if (mToast == null) {
851 mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
853 mToast.setText(resid);
857 private void startPlayback() {
861 Intent intent = getIntent();
862 String filename = "";
863 Uri uri = intent.getData();
864 if (uri != null && uri.toString().length() > 0) {
865 // If this is a file:// URI, just use the path directly instead
866 // of going through the open-from-filedescriptor codepath.
867 String scheme = uri.getScheme();
868 if ("file".equals(scheme)) {
869 filename = uri.getPath();
871 filename = uri.toString();
875 if (! mRelaunchAfterConfigChange) {
877 mService.openfile(filename);
880 } catch (Exception ex) {
881 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
886 long next = refreshNow();
887 queueNextRefresh(next);
890 private ServiceConnection osc = new ServiceConnection() {
891 public void onServiceConnected(ComponentName classname, IBinder obj) {
892 mService = IMediaPlaybackService.Stub.asInterface(obj);
893 if (MusicUtils.sService == null) {
894 MusicUtils.sService = mService;
898 // Assume something is playing when the service says it is,
899 // but also if the audio ID is valid but the service is paused.
900 if (mService.getAudioId() >= 0 || mService.isPlaying() ||
901 mService.getPath() != null) {
902 // something is playing now, we're done
904 mRepeatButton.setVisibility(View.INVISIBLE);
905 mShuffleButton.setVisibility(View.INVISIBLE);
906 mQueueButton.setVisibility(View.INVISIBLE);
908 mRepeatButton.setVisibility(View.VISIBLE);
909 mShuffleButton.setVisibility(View.VISIBLE);
910 mQueueButton.setVisibility(View.VISIBLE);
911 setRepeatButtonImage();
912 setShuffleButtonImage();
914 setPauseButtonImage();
917 } catch (RemoteException ex) {
919 // Service is dead or not playing anything. If we got here as part
920 // of a "play this file" Intent, exit. Otherwise go to the Music
922 if (getIntent().getData() == null) {
923 Intent intent = new Intent(Intent.ACTION_MAIN);
924 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
925 intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
926 startActivity(intent);
930 public void onServiceDisconnected(ComponentName classname) {
934 private void setRepeatButtonImage() {
936 switch (mService.getRepeatMode()) {
937 case MediaPlaybackService.REPEAT_ALL:
938 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
940 case MediaPlaybackService.REPEAT_CURRENT:
941 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
944 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
947 } catch (RemoteException ex) {
951 private void setShuffleButtonImage() {
953 switch (mService.getShuffleMode()) {
954 case MediaPlaybackService.SHUFFLE_NONE:
955 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
957 case MediaPlaybackService.SHUFFLE_AUTO:
958 mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
961 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
964 } catch (RemoteException ex) {
968 private void setPauseButtonImage() {
970 if (mService != null && mService.isPlaying()) {
971 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
973 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
975 } catch (RemoteException ex) {
979 private AlbumView mAlbum;
980 private TextView mCurrentTime;
981 private TextView mTotalTime;
982 private TextView mArtistName;
983 private TextView mAlbumName;
984 private TextView mTrackName;
985 private ProgressBar mProgress;
986 private long mPosOverride = -1;
987 private long mDuration;
988 private int seekmethod;
989 private boolean paused;
991 private static final int REFRESH = 1;
992 private static final int QUIT = 2;
993 private static final int GET_ALBUM_ART = 3;
994 private static final int ALBUM_ART_DECODED = 4;
996 private void queueNextRefresh(long delay) {
998 Message msg = mHandler.obtainMessage(REFRESH);
999 mHandler.removeMessages(REFRESH);
1000 mHandler.sendMessageDelayed(msg, delay);
1004 private long refreshNow() {
1005 if(mService == null)
1008 long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
1009 long remaining = 1000 - (pos % 1000);
1010 if ((pos >= 0) && (mDuration > 0)) {
1011 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
1013 if (mService.isPlaying()) {
1014 mCurrentTime.setVisibility(View.VISIBLE);
1016 // blink the counter
1017 int vis = mCurrentTime.getVisibility();
1018 mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1022 mProgress.setProgress((int) (1000 * pos / mDuration));
1024 mCurrentTime.setText("--:--");
1025 mProgress.setProgress(1000);
1027 // return the number of milliseconds until the next full second, so
1028 // the counter can be updated at just the right time
1030 } catch (RemoteException ex) {
1035 private final Handler mHandler = new Handler() {
1037 public void handleMessage(Message msg) {
1039 case ALBUM_ART_DECODED:
1040 mAlbum.setArtwork((Bitmap)msg.obj);
1041 mAlbum.invalidate();
1045 long next = refreshNow();
1046 queueNextRefresh(next);
1050 // This can be moved back to onCreate once the bug that prevents
1051 // Dialogs from being started from onCreate/onResume is fixed.
1052 new AlertDialog.Builder(MediaPlaybackActivity.this)
1053 .setTitle(R.string.service_start_error_title)
1054 .setMessage(R.string.service_start_error_msg)
1055 .setPositiveButton(R.string.service_start_error_button,
1056 new DialogInterface.OnClickListener() {
1057 public void onClick(DialogInterface dialog, int whichButton) {
1061 .setCancelable(false)
1071 private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1073 public void onReceive(Context context, Intent intent) {
1074 String action = intent.getAction();
1075 if (action.equals(MediaPlaybackService.META_CHANGED)) {
1076 // redraw the artist/title info and
1077 // set new max for progress bar
1079 setPauseButtonImage();
1080 queueNextRefresh(1);
1081 } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1085 setPauseButtonImage();
1087 } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1088 setPauseButtonImage();
1093 private void updateTrackInfo() {
1094 if (mService == null) {
1098 if (mService.getPath() == null) {
1102 String artistName = mService.getArtistName();
1103 if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1104 artistName = getString(R.string.unknown_artist_name);
1106 mArtistName.setText(artistName);
1107 String albumName = mService.getAlbumName();
1108 int albumid = mService.getAlbumId();
1109 if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
1110 albumName = getString(R.string.unknown_album_name);
1113 mAlbumName.setText(albumName);
1114 mTrackName.setText(mService.getTrackName());
1115 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1116 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, albumid, 0).sendToTarget();
1117 mDuration = mService.duration();
1118 mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1119 } catch (RemoteException ex) {
1124 public class AlbumArtHandler extends Handler {
1125 private int mAlbumId = -1;
1127 public AlbumArtHandler(Looper looper) {
1130 public void handleMessage(Message msg)
1132 int albumid = msg.arg1;
1133 if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1134 // while decoding the new image, show the default album art
1135 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1136 mHandler.removeMessages(ALBUM_ART_DECODED);
1137 mHandler.sendMessageDelayed(numsg, 300);
1138 Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, albumid);
1140 bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
1144 numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1145 mHandler.removeMessages(ALBUM_ART_DECODED);
1146 mHandler.sendMessage(numsg);
1153 private class Worker implements Runnable {
1154 private final Object mLock = new Object();
1155 private Looper mLooper;
1158 * Creates a worker thread with the given name. The thread
1159 * then runs a {@link android.os.Looper}.
1160 * @param name A name for the new thread
1162 Worker(String name) {
1163 Thread t = new Thread(null, this, name);
1164 t.setPriority(Thread.MIN_PRIORITY);
1166 synchronized (mLock) {
1167 while (mLooper == null) {
1170 } catch (InterruptedException ex) {
1176 public Looper getLooper() {
1181 synchronized (mLock) {
1183 mLooper = Looper.myLooper();
1189 public void quit() {