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.KeyguardManager;
22 import android.app.SearchManager;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.ServiceConnection;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.database.Cursor;
35 import android.graphics.Bitmap;
36 import android.media.AudioManager;
37 import android.media.MediaFile;
38 import android.net.Uri;
39 import android.os.Bundle;
40 import android.os.RemoteException;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Looper;
44 import android.os.Message;
45 import android.os.SystemClock;
46 import android.provider.MediaStore;
47 import android.text.Layout;
48 import android.text.TextUtils.TruncateAt;
49 import android.util.Log;
50 import android.view.KeyEvent;
51 import android.view.Menu;
52 import android.view.MenuItem;
53 import android.view.MotionEvent;
54 import android.view.SubMenu;
55 import android.view.View;
56 import android.view.ViewConfiguration;
57 import android.view.Window;
58 import android.view.WindowManager;
59 import android.widget.ImageButton;
60 import android.widget.ImageView;
61 import android.widget.ProgressBar;
62 import android.widget.SeekBar;
63 import android.widget.TextView;
64 import android.widget.Toast;
65 import android.widget.SeekBar.OnSeekBarChangeListener;
68 public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs,
69 View.OnTouchListener, View.OnLongClickListener
71 private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
73 private boolean mOneShot = false;
74 private boolean mSeeking = false;
75 private boolean mDeviceHasDpad;
76 private long mStartSeekPos = 0;
77 private long mLastSeekEventTime;
78 private IMediaPlaybackService mService = null;
79 private RepeatingImageButton mPrevButton;
80 private ImageButton mPauseButton;
81 private RepeatingImageButton mNextButton;
82 private ImageButton mRepeatButton;
83 private ImageButton mShuffleButton;
84 private ImageButton mQueueButton;
85 private Worker mAlbumArtWorker;
86 private AlbumArtHandler mAlbumArtHandler;
88 private int mTouchSlop;
90 public MediaPlaybackActivity()
94 /** Called when the activity is first created. */
96 public void onCreate(Bundle icicle)
98 super.onCreate(icicle);
99 setVolumeControlStream(AudioManager.STREAM_MUSIC);
101 mAlbumArtWorker = new Worker("album art worker");
102 mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
104 requestWindowFeature(Window.FEATURE_NO_TITLE);
105 setContentView(R.layout.audio_player);
107 mCurrentTime = (TextView) findViewById(R.id.currenttime);
108 mTotalTime = (TextView) findViewById(R.id.totaltime);
109 mProgress = (ProgressBar) findViewById(android.R.id.progress);
110 mAlbum = (ImageView) findViewById(R.id.album);
111 mArtistName = (TextView) findViewById(R.id.artistname);
112 mAlbumName = (TextView) findViewById(R.id.albumname);
113 mTrackName = (TextView) findViewById(R.id.trackname);
115 View v = (View)mArtistName.getParent();
116 v.setOnTouchListener(this);
117 v.setOnLongClickListener(this);
119 v = (View)mAlbumName.getParent();
120 v.setOnTouchListener(this);
121 v.setOnLongClickListener(this);
123 v = (View)mTrackName.getParent();
124 v.setOnTouchListener(this);
125 v.setOnLongClickListener(this);
127 mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
128 mPrevButton.setOnClickListener(mPrevListener);
129 mPrevButton.setRepeatListener(mRewListener, 260);
130 mPauseButton = (ImageButton) findViewById(R.id.pause);
131 mPauseButton.requestFocus();
132 mPauseButton.setOnClickListener(mPauseListener);
133 mNextButton = (RepeatingImageButton) findViewById(R.id.next);
134 mNextButton.setOnClickListener(mNextListener);
135 mNextButton.setRepeatListener(mFfwdListener, 260);
138 mDeviceHasDpad = (getResources().getConfiguration().navigation ==
139 Configuration.NAVIGATION_DPAD);
141 mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
142 mQueueButton.setOnClickListener(mQueueListener);
143 mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
144 mShuffleButton.setOnClickListener(mShuffleListener);
145 mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
146 mRepeatButton.setOnClickListener(mRepeatListener);
148 if (mProgress instanceof SeekBar) {
149 SeekBar seeker = (SeekBar) mProgress;
150 seeker.setOnSeekBarChangeListener(mSeekListener);
152 mProgress.setMax(1000);
154 if (icicle != null) {
155 mOneShot = icicle.getBoolean("oneshot");
157 mOneShot = getIntent().getBooleanExtra("oneshot", false);
160 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
167 boolean mDraggingLabel = false;
169 TextView textViewForContainer(View v) {
170 View vv = v.findViewById(R.id.artistname);
171 if (vv != null) return (TextView) vv;
172 vv = v.findViewById(R.id.albumname);
173 if (vv != null) return (TextView) vv;
174 vv = v.findViewById(R.id.trackname);
175 if (vv != null) return (TextView) vv;
179 public boolean onTouch(View v, MotionEvent event) {
180 int action = event.getAction();
181 TextView tv = textViewForContainer(v);
185 if (action == MotionEvent.ACTION_DOWN) {
186 v.setBackgroundColor(0xff606060);
187 mInitialX = mLastX = (int) event.getX();
188 mDraggingLabel = false;
189 } else if (action == MotionEvent.ACTION_UP ||
190 action == MotionEvent.ACTION_CANCEL) {
191 v.setBackgroundColor(0);
192 if (mDraggingLabel) {
193 Message msg = mLabelScroller.obtainMessage(0, tv);
194 mLabelScroller.sendMessageDelayed(msg, 1000);
196 } else if (action == MotionEvent.ACTION_MOVE) {
197 if (mDraggingLabel) {
198 int scrollx = tv.getScrollX();
199 int x = (int) event.getX();
200 int delta = mLastX - x;
204 if (scrollx > mTextWidth) {
205 // scrolled the text completely off the view to the left
206 scrollx -= mTextWidth;
207 scrollx -= mViewWidth;
209 if (scrollx < -mViewWidth) {
210 // scrolled the text completely off the view to the right
211 scrollx += mViewWidth;
212 scrollx += mTextWidth;
214 tv.scrollTo(scrollx, 0);
218 int delta = mInitialX - (int) event.getX();
219 if (Math.abs(delta) > mTouchSlop) {
221 mLabelScroller.removeMessages(0, tv);
223 // Only turn ellipsizing off when it's not already off, because it
224 // causes the scroll position to be reset to 0.
225 if (tv.getEllipsize() != null) {
226 tv.setEllipsize(null);
228 Layout ll = tv.getLayout();
229 // layout might be null if the text just changed, or ellipsizing
230 // was just turned off
234 // get the non-ellipsized line width, to determine whether scrolling
235 // should even be allowed
236 mTextWidth = (int) tv.getLayout().getLineWidth(0);
237 mViewWidth = tv.getWidth();
238 if (mViewWidth > mTextWidth) {
239 tv.setEllipsize(TruncateAt.END);
243 mDraggingLabel = true;
244 tv.setHorizontalFadingEdgeEnabled(true);
252 Handler mLabelScroller = new Handler() {
254 public void handleMessage(Message msg) {
255 TextView tv = (TextView) msg.obj;
256 int x = tv.getScrollX();
260 tv.setEllipsize(TruncateAt.END);
262 Message newmsg = obtainMessage(0, tv);
263 mLabelScroller.sendMessageDelayed(newmsg, 15);
268 public boolean onLongClick(View view) {
270 CharSequence title = null;
279 artist = mService.getArtistName();
280 album = mService.getAlbumName();
281 song = mService.getTrackName();
282 audioid = mService.getAudioId();
283 } catch (RemoteException ex) {
285 } catch (NullPointerException ex) {
286 // we might not actually have the service yet
290 if (MediaFile.UNKNOWN_STRING.equals(album) &&
291 MediaFile.UNKNOWN_STRING.equals(artist) &&
293 song.startsWith("recording")) {
302 Cursor c = MusicUtils.query(this,
303 ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioid),
304 new String[] {MediaStore.Audio.Media.IS_MUSIC}, null, null, null);
305 boolean ismusic = true;
307 if (c.moveToFirst()) {
308 ismusic = c.getInt(0) != 0;
316 boolean knownartist =
317 (artist != null) && !MediaFile.UNKNOWN_STRING.equals(artist);
320 (album != null) && !MediaFile.UNKNOWN_STRING.equals(album);
322 if (knownartist && view.equals(mArtistName.getParent())) {
325 mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
326 } else if (knownalbum && view.equals(mAlbumName.getParent())) {
329 query = artist + " " + album;
333 mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
334 } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
335 if ((song == null) || MediaFile.UNKNOWN_STRING.equals(song)) {
336 // A popup of the form "Search for null/'' using ..." is pretty
337 // unhelpful, plus, we won't find any way to buy it anyway.
343 query = artist + " " + song;
347 mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
349 throw new RuntimeException("shouldn't be here");
351 title = getString(R.string.mediasearch, title);
353 Intent i = new Intent();
354 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
355 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
356 i.putExtra(SearchManager.QUERY, query);
358 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
361 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
363 i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
364 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
366 startActivity(Intent.createChooser(i, title));
370 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
371 public void onStartTrackingTouch(SeekBar bar) {
372 mLastSeekEventTime = 0;
375 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
376 if (!fromuser || (mService == null)) return;
377 long now = SystemClock.elapsedRealtime();
378 if ((now - mLastSeekEventTime) > 250) {
379 mLastSeekEventTime = now;
380 mPosOverride = mDuration * progress / 1000;
382 mService.seek(mPosOverride);
383 } catch (RemoteException ex) {
386 // trackball event, allow progress updates
393 public void onStopTrackingTouch(SeekBar bar) {
399 private View.OnClickListener mQueueListener = new View.OnClickListener() {
400 public void onClick(View v) {
402 new Intent(Intent.ACTION_EDIT)
403 .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
404 .putExtra("playlist", "nowplaying")
409 private View.OnClickListener mShuffleListener = new View.OnClickListener() {
410 public void onClick(View v) {
415 private View.OnClickListener mRepeatListener = new View.OnClickListener() {
416 public void onClick(View v) {
421 private View.OnClickListener mPauseListener = new View.OnClickListener() {
422 public void onClick(View v) {
427 private View.OnClickListener mPrevListener = new View.OnClickListener() {
428 public void onClick(View v) {
429 if (mService == null) return;
431 if (mService.position() < 2000) {
437 } catch (RemoteException ex) {
442 private View.OnClickListener mNextListener = new View.OnClickListener() {
443 public void onClick(View v) {
444 if (mService == null) return;
447 } catch (RemoteException ex) {
452 private RepeatingImageButton.RepeatListener mRewListener =
453 new RepeatingImageButton.RepeatListener() {
454 public void onRepeat(View v, long howlong, int repcnt) {
455 scanBackward(repcnt, howlong);
459 private RepeatingImageButton.RepeatListener mFfwdListener =
460 new RepeatingImageButton.RepeatListener() {
461 public void onRepeat(View v, long howlong, int repcnt) {
462 scanForward(repcnt, howlong);
467 public void onStop() {
469 if (mService != null && mOneShot && getChangingConfigurations() == 0) {
472 } catch (RemoteException ex) {
475 mHandler.removeMessages(REFRESH);
476 unregisterReceiver(mStatusListener);
477 MusicUtils.unbindFromService(this);
483 public void onSaveInstanceState(Bundle outState) {
484 outState.putBoolean("oneshot", mOneShot);
485 super.onSaveInstanceState(outState);
489 public void onStart() {
493 if (false == MusicUtils.bindToService(this, osc)) {
494 // something went wrong
495 mHandler.sendEmptyMessage(QUIT);
498 IntentFilter f = new IntentFilter();
499 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
500 f.addAction(MediaPlaybackService.META_CHANGED);
501 f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
502 registerReceiver(mStatusListener, new IntentFilter(f));
504 long next = refreshNow();
505 queueNextRefresh(next);
509 public void onNewIntent(Intent intent) {
511 mOneShot = intent.getBooleanExtra("oneshot", false);
515 public void onResume() {
518 setPauseButtonImage();
522 public void onDestroy()
524 mAlbumArtWorker.quit();
526 //System.out.println("***************** playback activity onDestroy\n");
530 public boolean onCreateOptionsMenu(Menu menu) {
531 super.onCreateOptionsMenu(menu);
532 // Don't show the menu items if we got launched by path/filedescriptor, or
533 // if we're in one shot mode. In most cases, these menu items are not
534 // useful in those modes, so for consistency we never show them in these
535 // modes, instead of tailoring them to the specific file being played.
536 if (MusicUtils.getCurrentAudioId() >= 0 && !mOneShot) {
537 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
538 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
539 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
540 R.string.add_to_playlist).setIcon(android.R.drawable.ic_menu_add);
541 // these next two are in a separate group, so they can be shown/hidden as needed
542 // based on the keyguard state
543 menu.add(1, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short)
544 .setIcon(R.drawable.ic_menu_set_as_ringtone);
545 menu.add(1, DELETE_ITEM, 0, R.string.delete_item)
546 .setIcon(R.drawable.ic_menu_delete);
553 public boolean onPrepareOptionsMenu(Menu menu) {
554 MenuItem item = menu.findItem(PARTY_SHUFFLE);
556 int shuffle = MusicUtils.getCurrentShuffleMode();
557 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
558 item.setIcon(R.drawable.ic_menu_party_shuffle);
559 item.setTitle(R.string.party_shuffle_off);
561 item.setIcon(R.drawable.ic_menu_party_shuffle);
562 item.setTitle(R.string.party_shuffle);
566 item = menu.findItem(ADD_TO_PLAYLIST);
568 SubMenu sub = item.getSubMenu();
569 MusicUtils.makePlaylistMenu(this, sub);
572 KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
573 menu.setGroupVisible(1, !km.inKeyguardRestrictedInputMode());
579 public boolean onOptionsItemSelected(MenuItem item) {
582 switch (item.getItemId()) {
584 intent = new Intent();
585 intent.setClass(this, MusicBrowserActivity.class);
586 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
587 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
588 startActivity(intent);
590 case USE_AS_RINGTONE: {
591 // Set the system setting to make this the current ringtone
592 if (mService != null) {
593 MusicUtils.setRingtone(this, mService.getAudioId());
598 if (mService != null) {
599 int shuffle = mService.getShuffleMode();
600 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
601 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
603 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
606 setShuffleButtonImage();
610 intent = new Intent();
611 intent.setClass(this, CreatePlaylist.class);
612 startActivityForResult(intent, NEW_PLAYLIST);
616 case PLAYLIST_SELECTED: {
617 long [] list = new long[1];
618 list[0] = MusicUtils.getCurrentAudioId();
619 long playlist = item.getIntent().getLongExtra("playlist", 0);
620 MusicUtils.addToPlaylist(this, list, playlist);
625 if (mService != null) {
626 long [] list = new long[1];
627 list[0] = MusicUtils.getCurrentAudioId();
628 Bundle b = new Bundle();
629 b.putString("description", getString(R.string.delete_song_desc,
630 mService.getTrackName()));
631 b.putLongArray("items", list);
632 intent = new Intent();
633 intent.setClass(this, DeleteItems.class);
635 startActivityForResult(intent, -1);
640 } catch (RemoteException ex) {
642 return super.onOptionsItemSelected(item);
646 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
647 if (resultCode != RESULT_OK) {
650 switch (requestCode) {
652 Uri uri = intent.getData();
654 long [] list = new long[1];
655 list[0] = MusicUtils.getCurrentAudioId();
656 int playlist = Integer.parseInt(uri.getLastPathSegment());
657 MusicUtils.addToPlaylist(this, list, playlist);
662 private final int keyboard[][] = {
685 KeyEvent.KEYCODE_DEL,
695 KeyEvent.KEYCODE_COMMA,
696 KeyEvent.KEYCODE_PERIOD,
697 KeyEvent.KEYCODE_ENTER
705 private boolean seekMethod1(int keyCode)
707 if (mService == null) return false;
708 for(int x=0;x<10;x++) {
709 for(int y=0;y<3;y++) {
710 if(keyboard[y][x] == keyCode) {
713 if(x == lastX && y == lastY) dir = 0;
714 else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
715 else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
717 else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
718 else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
720 else if (y < lastY && x <= 4) dir = 1;
721 else if (y < lastY && x >= 5) dir = -1;
723 else if (y > lastY && x <= 4) dir = -1;
724 else if (y > lastY && x >= 5) dir = 1;
728 mService.seek(mService.position() + dir * 5);
729 } catch (RemoteException ex) {
741 private boolean seekMethod2(int keyCode)
743 if (mService == null) return false;
744 for(int i=0;i<10;i++) {
745 if(keyboard[0][i] == keyCode) {
746 int seekpercentage = 100*i/10;
748 mService.seek(mService.duration() * seekpercentage / 100);
749 } catch (RemoteException ex) {
759 public boolean onKeyUp(int keyCode, KeyEvent event) {
763 case KeyEvent.KEYCODE_DPAD_LEFT:
764 if (!useDpadMusicControl()) {
767 if (mService != null) {
768 if (!mSeeking && mStartSeekPos >= 0) {
769 mPauseButton.requestFocus();
770 if (mStartSeekPos < 1000) {
776 scanBackward(-1, event.getEventTime() - event.getDownTime());
777 mPauseButton.requestFocus();
784 case KeyEvent.KEYCODE_DPAD_RIGHT:
785 if (!useDpadMusicControl()) {
788 if (mService != null) {
789 if (!mSeeking && mStartSeekPos >= 0) {
790 mPauseButton.requestFocus();
793 scanForward(-1, event.getEventTime() - event.getDownTime());
794 mPauseButton.requestFocus();
802 } catch (RemoteException ex) {
804 return super.onKeyUp(keyCode, event);
807 private boolean useDpadMusicControl() {
808 if (mDeviceHasDpad && (mPrevButton.isFocused() ||
809 mNextButton.isFocused() ||
810 mPauseButton.isFocused())) {
817 public boolean onKeyDown(int keyCode, KeyEvent event)
820 int repcnt = event.getRepeatCount();
822 if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
829 case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
830 case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
832 case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
833 case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
834 case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
835 case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
837 case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
838 case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
840 case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
841 case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
842 case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
843 case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
847 case KeyEvent.KEYCODE_SLASH:
848 seekmethod = 1 - seekmethod;
851 case KeyEvent.KEYCODE_DPAD_LEFT:
852 if (!useDpadMusicControl()) {
855 if (!mPrevButton.hasFocus()) {
856 mPrevButton.requestFocus();
858 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
860 case KeyEvent.KEYCODE_DPAD_RIGHT:
861 if (!useDpadMusicControl()) {
864 if (!mNextButton.hasFocus()) {
865 mNextButton.requestFocus();
867 scanForward(repcnt, event.getEventTime() - event.getDownTime());
870 case KeyEvent.KEYCODE_S:
874 case KeyEvent.KEYCODE_DPAD_CENTER:
875 case KeyEvent.KEYCODE_SPACE:
879 return super.onKeyDown(keyCode, event);
882 private void scanBackward(int repcnt, long delta) {
883 if(mService == null) return;
886 mStartSeekPos = mService.position();
887 mLastSeekEventTime = 0;
892 // seek at 10x speed for the first 5 seconds
895 // seek at 40x after that
896 delta = 50000 + (delta - 5000) * 40;
898 long newpos = mStartSeekPos - delta;
900 // move to previous track
902 long duration = mService.duration();
903 mStartSeekPos += duration;
906 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
907 mService.seek(newpos);
908 mLastSeekEventTime = delta;
911 mPosOverride = newpos;
917 } catch (RemoteException ex) {
921 private void scanForward(int repcnt, long delta) {
922 if(mService == null) return;
925 mStartSeekPos = mService.position();
926 mLastSeekEventTime = 0;
931 // seek at 10x speed for the first 5 seconds
934 // seek at 40x after that
935 delta = 50000 + (delta - 5000) * 40;
937 long newpos = mStartSeekPos + delta;
938 long duration = mService.duration();
939 if (newpos >= duration) {
940 // move to next track
942 mStartSeekPos -= duration; // is OK to go negative
945 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
946 mService.seek(newpos);
947 mLastSeekEventTime = delta;
950 mPosOverride = newpos;
956 } catch (RemoteException ex) {
960 private void doPauseResume() {
962 if(mService != null) {
963 if (mService.isPlaying()) {
969 setPauseButtonImage();
971 } catch (RemoteException ex) {
975 private void toggleShuffle() {
976 if (mService == null) {
980 int shuffle = mService.getShuffleMode();
981 if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
982 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
983 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
984 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
985 setRepeatButtonImage();
987 showToast(R.string.shuffle_on_notif);
988 } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
989 shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
990 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
991 showToast(R.string.shuffle_off_notif);
993 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
995 setShuffleButtonImage();
996 } catch (RemoteException ex) {
1000 private void cycleRepeat() {
1001 if (mService == null) {
1005 int mode = mService.getRepeatMode();
1006 if (mode == MediaPlaybackService.REPEAT_NONE) {
1007 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
1008 showToast(R.string.repeat_all_notif);
1009 } else if (mode == MediaPlaybackService.REPEAT_ALL) {
1010 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
1011 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
1012 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
1013 setShuffleButtonImage();
1015 showToast(R.string.repeat_current_notif);
1017 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
1018 showToast(R.string.repeat_off_notif);
1020 setRepeatButtonImage();
1021 } catch (RemoteException ex) {
1026 private void showToast(int resid) {
1027 if (mToast == null) {
1028 mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
1030 mToast.setText(resid);
1034 private void startPlayback() {
1036 if(mService == null)
1038 Intent intent = getIntent();
1039 String filename = "";
1040 Uri uri = intent.getData();
1041 if (uri != null && uri.toString().length() > 0) {
1042 // If this is a file:// URI, just use the path directly instead
1043 // of going through the open-from-filedescriptor codepath.
1044 String scheme = uri.getScheme();
1045 if ("file".equals(scheme)) {
1046 filename = uri.getPath();
1048 filename = uri.toString();
1051 if (! ContentResolver.SCHEME_CONTENT.equals(scheme) ||
1052 ! MediaStore.AUTHORITY.equals(uri.getAuthority())) {
1056 mService.openFile(filename, mOneShot);
1058 setIntent(new Intent());
1059 } catch (Exception ex) {
1060 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
1065 long next = refreshNow();
1066 queueNextRefresh(next);
1069 private ServiceConnection osc = new ServiceConnection() {
1070 public void onServiceConnected(ComponentName classname, IBinder obj) {
1071 mService = IMediaPlaybackService.Stub.asInterface(obj);
1074 // Assume something is playing when the service says it is,
1075 // but also if the audio ID is valid but the service is paused.
1076 if (mService.getAudioId() >= 0 || mService.isPlaying() ||
1077 mService.getPath() != null) {
1078 // something is playing now, we're done
1079 if (mOneShot || mService.getAudioId() < 0) {
1080 mRepeatButton.setVisibility(View.INVISIBLE);
1081 mShuffleButton.setVisibility(View.INVISIBLE);
1082 mQueueButton.setVisibility(View.INVISIBLE);
1084 mRepeatButton.setVisibility(View.VISIBLE);
1085 mShuffleButton.setVisibility(View.VISIBLE);
1086 mQueueButton.setVisibility(View.VISIBLE);
1087 setRepeatButtonImage();
1088 setShuffleButtonImage();
1090 setPauseButtonImage();
1093 } catch (RemoteException ex) {
1095 // Service is dead or not playing anything. If we got here as part
1096 // of a "play this file" Intent, exit. Otherwise go to the Music
1097 // app start screen.
1098 if (getIntent().getData() == null) {
1099 Intent intent = new Intent(Intent.ACTION_MAIN);
1100 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1101 intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
1102 startActivity(intent);
1106 public void onServiceDisconnected(ComponentName classname) {
1110 private void setRepeatButtonImage() {
1112 switch (mService.getRepeatMode()) {
1113 case MediaPlaybackService.REPEAT_ALL:
1114 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
1116 case MediaPlaybackService.REPEAT_CURRENT:
1117 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
1120 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
1123 } catch (RemoteException ex) {
1127 private void setShuffleButtonImage() {
1129 switch (mService.getShuffleMode()) {
1130 case MediaPlaybackService.SHUFFLE_NONE:
1131 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
1133 case MediaPlaybackService.SHUFFLE_AUTO:
1134 mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
1137 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
1140 } catch (RemoteException ex) {
1144 private void setPauseButtonImage() {
1146 if (mService != null && mService.isPlaying()) {
1147 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
1149 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
1151 } catch (RemoteException ex) {
1155 private ImageView mAlbum;
1156 private TextView mCurrentTime;
1157 private TextView mTotalTime;
1158 private TextView mArtistName;
1159 private TextView mAlbumName;
1160 private TextView mTrackName;
1161 private ProgressBar mProgress;
1162 private long mPosOverride = -1;
1163 private boolean mFromTouch = false;
1164 private long mDuration;
1165 private int seekmethod;
1166 private boolean paused;
1168 private static final int REFRESH = 1;
1169 private static final int QUIT = 2;
1170 private static final int GET_ALBUM_ART = 3;
1171 private static final int ALBUM_ART_DECODED = 4;
1173 private void queueNextRefresh(long delay) {
1175 Message msg = mHandler.obtainMessage(REFRESH);
1176 mHandler.removeMessages(REFRESH);
1177 mHandler.sendMessageDelayed(msg, delay);
1181 private long refreshNow() {
1182 if(mService == null)
1185 long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
1186 long remaining = 1000 - (pos % 1000);
1187 if ((pos >= 0) && (mDuration > 0)) {
1188 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
1190 if (mService.isPlaying()) {
1191 mCurrentTime.setVisibility(View.VISIBLE);
1193 // blink the counter
1194 int vis = mCurrentTime.getVisibility();
1195 mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1199 mProgress.setProgress((int) (1000 * pos / mDuration));
1201 mCurrentTime.setText("--:--");
1202 mProgress.setProgress(1000);
1204 // return the number of milliseconds until the next full second, so
1205 // the counter can be updated at just the right time
1207 } catch (RemoteException ex) {
1212 private final Handler mHandler = new Handler() {
1214 public void handleMessage(Message msg) {
1216 case ALBUM_ART_DECODED:
1217 mAlbum.setImageBitmap((Bitmap)msg.obj);
1218 mAlbum.getDrawable().setDither(true);
1222 long next = refreshNow();
1223 queueNextRefresh(next);
1227 // This can be moved back to onCreate once the bug that prevents
1228 // Dialogs from being started from onCreate/onResume is fixed.
1229 new AlertDialog.Builder(MediaPlaybackActivity.this)
1230 .setTitle(R.string.service_start_error_title)
1231 .setMessage(R.string.service_start_error_msg)
1232 .setPositiveButton(R.string.service_start_error_button,
1233 new DialogInterface.OnClickListener() {
1234 public void onClick(DialogInterface dialog, int whichButton) {
1238 .setCancelable(false)
1248 private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1250 public void onReceive(Context context, Intent intent) {
1251 String action = intent.getAction();
1252 if (action.equals(MediaPlaybackService.META_CHANGED)) {
1253 // redraw the artist/title info and
1254 // set new max for progress bar
1256 setPauseButtonImage();
1257 queueNextRefresh(1);
1258 } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1262 setPauseButtonImage();
1264 } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1265 setPauseButtonImage();
1270 private static class AlbumSongIdWrapper {
1271 public long albumid;
1273 AlbumSongIdWrapper(long aid, long sid) {
1279 private void updateTrackInfo() {
1280 if (mService == null) {
1284 String path = mService.getPath();
1290 long songid = mService.getAudioId();
1291 if (songid < 0 && path.toLowerCase().startsWith("http://")) {
1292 // Once we can get album art and meta data from MediaPlayer, we
1293 // can show that info again when streaming.
1294 ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
1295 ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
1296 mAlbum.setVisibility(View.GONE);
1297 mTrackName.setText(path);
1298 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1299 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(-1, -1)).sendToTarget();
1301 ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
1302 ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
1303 String artistName = mService.getArtistName();
1304 if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1305 artistName = getString(R.string.unknown_artist_name);
1307 mArtistName.setText(artistName);
1308 String albumName = mService.getAlbumName();
1309 long albumid = mService.getAlbumId();
1310 if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
1311 albumName = getString(R.string.unknown_album_name);
1314 mAlbumName.setText(albumName);
1315 mTrackName.setText(mService.getTrackName());
1316 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1317 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(albumid, songid)).sendToTarget();
1318 mAlbum.setVisibility(View.VISIBLE);
1320 mDuration = mService.duration();
1321 mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1322 } catch (RemoteException ex) {
1327 public class AlbumArtHandler extends Handler {
1328 private long mAlbumId = -1;
1330 public AlbumArtHandler(Looper looper) {
1334 public void handleMessage(Message msg)
1336 long albumid = ((AlbumSongIdWrapper) msg.obj).albumid;
1337 long songid = ((AlbumSongIdWrapper) msg.obj).songid;
1338 if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1339 // while decoding the new image, show the default album art
1340 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1341 mHandler.removeMessages(ALBUM_ART_DECODED);
1342 mHandler.sendMessageDelayed(numsg, 300);
1343 Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, albumid);
1345 bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, -1);
1349 numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1350 mHandler.removeMessages(ALBUM_ART_DECODED);
1351 mHandler.sendMessage(numsg);
1358 private static class Worker implements Runnable {
1359 private final Object mLock = new Object();
1360 private Looper mLooper;
1363 * Creates a worker thread with the given name. The thread
1364 * then runs a {@link android.os.Looper}.
1365 * @param name A name for the new thread
1367 Worker(String name) {
1368 Thread t = new Thread(null, this, name);
1369 t.setPriority(Thread.MIN_PRIORITY);
1371 synchronized (mLock) {
1372 while (mLooper == null) {
1375 } catch (InterruptedException ex) {
1381 public Looper getLooper() {
1386 synchronized (mLock) {
1388 mLooper = Looper.myLooper();
1394 public void quit() {