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.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.ServiceConnection;
31 import android.content.res.Configuration;
32 import android.content.res.Resources;
33 import android.database.Cursor;
34 import android.graphics.Bitmap;
35 import android.media.AudioManager;
36 import android.media.MediaFile;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.RemoteException;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Looper;
43 import android.os.Message;
44 import android.os.SystemClock;
45 import android.provider.MediaStore;
46 import android.text.Layout;
47 import android.text.TextUtils.TruncateAt;
48 import android.util.Log;
49 import android.view.KeyEvent;
50 import android.view.Menu;
51 import android.view.MenuItem;
52 import android.view.MotionEvent;
53 import android.view.SubMenu;
54 import android.view.View;
55 import android.view.ViewConfiguration;
56 import android.view.Window;
57 import android.widget.ImageButton;
58 import android.widget.ImageView;
59 import android.widget.ProgressBar;
60 import android.widget.SeekBar;
61 import android.widget.TextView;
62 import android.widget.Toast;
63 import android.widget.SeekBar.OnSeekBarChangeListener;
66 public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs,
67 View.OnTouchListener, View.OnLongClickListener
69 private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
71 private boolean mOneShot = false;
72 private boolean mSeeking = false;
73 private boolean mDeviceHasDpad;
74 private long mStartSeekPos = 0;
75 private long mLastSeekEventTime;
76 private IMediaPlaybackService mService = null;
77 private RepeatingImageButton mPrevButton;
78 private ImageButton mPauseButton;
79 private RepeatingImageButton mNextButton;
80 private ImageButton mRepeatButton;
81 private ImageButton mShuffleButton;
82 private ImageButton mQueueButton;
83 private Worker mAlbumArtWorker;
84 private AlbumArtHandler mAlbumArtHandler;
86 private int mTouchSlop;
88 public MediaPlaybackActivity()
92 /** Called when the activity is first created. */
94 public void onCreate(Bundle icicle)
96 super.onCreate(icicle);
97 setVolumeControlStream(AudioManager.STREAM_MUSIC);
99 mAlbumArtWorker = new Worker("album art worker");
100 mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
102 requestWindowFeature(Window.FEATURE_NO_TITLE);
103 setContentView(R.layout.audio_player);
105 mCurrentTime = (TextView) findViewById(R.id.currenttime);
106 mTotalTime = (TextView) findViewById(R.id.totaltime);
107 mProgress = (ProgressBar) findViewById(android.R.id.progress);
108 mAlbum = (ImageView) findViewById(R.id.album);
109 mArtistName = (TextView) findViewById(R.id.artistname);
110 mAlbumName = (TextView) findViewById(R.id.albumname);
111 mTrackName = (TextView) findViewById(R.id.trackname);
113 View v = (View)mArtistName.getParent();
114 v.setOnTouchListener(this);
115 v.setOnLongClickListener(this);
117 v = (View)mAlbumName.getParent();
118 v.setOnTouchListener(this);
119 v.setOnLongClickListener(this);
121 v = (View)mTrackName.getParent();
122 v.setOnTouchListener(this);
123 v.setOnLongClickListener(this);
125 mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
126 mPrevButton.setOnClickListener(mPrevListener);
127 mPrevButton.setRepeatListener(mRewListener, 260);
128 mPauseButton = (ImageButton) findViewById(R.id.pause);
129 mPauseButton.requestFocus();
130 mPauseButton.setOnClickListener(mPauseListener);
131 mNextButton = (RepeatingImageButton) findViewById(R.id.next);
132 mNextButton.setOnClickListener(mNextListener);
133 mNextButton.setRepeatListener(mFfwdListener, 260);
136 mDeviceHasDpad = (getResources().getConfiguration().navigation ==
137 Configuration.NAVIGATION_DPAD);
139 mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
140 mQueueButton.setOnClickListener(mQueueListener);
141 mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
142 mShuffleButton.setOnClickListener(mShuffleListener);
143 mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
144 mRepeatButton.setOnClickListener(mRepeatListener);
146 if (mProgress instanceof SeekBar) {
147 SeekBar seeker = (SeekBar) mProgress;
148 seeker.setOnSeekBarChangeListener(mSeekListener);
150 mProgress.setMax(1000);
152 if (icicle != null) {
153 mOneShot = icicle.getBoolean("oneshot");
155 mOneShot = getIntent().getBooleanExtra("oneshot", false);
158 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
165 boolean mDraggingLabel = false;
167 TextView textViewForContainer(View v) {
168 View vv = v.findViewById(R.id.artistname);
169 if (vv != null) return (TextView) vv;
170 vv = v.findViewById(R.id.albumname);
171 if (vv != null) return (TextView) vv;
172 vv = v.findViewById(R.id.trackname);
173 if (vv != null) return (TextView) vv;
177 public boolean onTouch(View v, MotionEvent event) {
178 int action = event.getAction();
179 TextView tv = textViewForContainer(v);
183 if (action == MotionEvent.ACTION_DOWN) {
184 v.setBackgroundColor(0xff606060);
185 mInitialX = mLastX = (int) event.getX();
186 mDraggingLabel = false;
187 } else if (action == MotionEvent.ACTION_UP ||
188 action == MotionEvent.ACTION_CANCEL) {
189 v.setBackgroundColor(0);
190 if (mDraggingLabel) {
191 Message msg = mLabelScroller.obtainMessage(0, tv);
192 mLabelScroller.sendMessageDelayed(msg, 1000);
194 } else if (action == MotionEvent.ACTION_MOVE) {
195 if (mDraggingLabel) {
196 int scrollx = tv.getScrollX();
197 int x = (int) event.getX();
198 int delta = mLastX - x;
202 if (scrollx > mTextWidth) {
203 // scrolled the text completely off the view to the left
204 scrollx -= mTextWidth;
205 scrollx -= mViewWidth;
207 if (scrollx < -mViewWidth) {
208 // scrolled the text completely off the view to the right
209 scrollx += mViewWidth;
210 scrollx += mTextWidth;
212 tv.scrollTo(scrollx, 0);
216 int delta = mInitialX - (int) event.getX();
217 if (Math.abs(delta) > mTouchSlop) {
219 mLabelScroller.removeMessages(0, tv);
221 // Only turn ellipsizing off when it's not already off, because it
222 // causes the scroll position to be reset to 0.
223 if (tv.getEllipsize() != null) {
224 tv.setEllipsize(null);
226 Layout ll = tv.getLayout();
227 // layout might be null if the text just changed, or ellipsizing
228 // was just turned off
232 // get the non-ellipsized line width, to determine whether scrolling
233 // should even be allowed
234 mTextWidth = (int) tv.getLayout().getLineWidth(0);
235 mViewWidth = tv.getWidth();
236 if (mViewWidth > mTextWidth) {
237 tv.setEllipsize(TruncateAt.END);
241 mDraggingLabel = true;
242 tv.setHorizontalFadingEdgeEnabled(true);
250 Handler mLabelScroller = new Handler() {
252 public void handleMessage(Message msg) {
253 TextView tv = (TextView) msg.obj;
254 int x = tv.getScrollX();
258 tv.setEllipsize(TruncateAt.END);
260 Message newmsg = obtainMessage(0, tv);
261 mLabelScroller.sendMessageDelayed(newmsg, 15);
266 public boolean onLongClick(View view) {
268 CharSequence title = null;
277 artist = mService.getArtistName();
278 album = mService.getAlbumName();
279 song = mService.getTrackName();
280 audioid = mService.getAudioId();
281 } catch (RemoteException ex) {
285 if (MediaFile.UNKNOWN_STRING.equals(album) &&
286 MediaFile.UNKNOWN_STRING.equals(artist) &&
288 song.startsWith("recording")) {
297 Cursor c = MusicUtils.query(this,
298 ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioid),
299 new String[] {MediaStore.Audio.Media.IS_MUSIC}, null, null, null);
300 boolean ismusic = true;
302 if (c.moveToFirst()) {
303 ismusic = c.getInt(0) != 0;
311 boolean knownartist =
312 (artist != null) && !MediaFile.UNKNOWN_STRING.equals(artist);
315 (album != null) && !MediaFile.UNKNOWN_STRING.equals(album);
317 if (knownartist && view.equals(mArtistName.getParent())) {
320 mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
321 } else if (knownalbum && view.equals(mAlbumName.getParent())) {
324 query = artist + " " + album;
328 mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
329 } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
330 if ((song == null) || MediaFile.UNKNOWN_STRING.equals(song)) {
331 // A popup of the form "Search for null/'' using ..." is pretty
332 // unhelpful, plus, we won't find any way to buy it anyway.
338 query = artist + " " + song;
342 mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
344 throw new RuntimeException("shouldn't be here");
346 title = getString(R.string.mediasearch, title);
348 Intent i = new Intent();
349 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
350 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
351 i.putExtra(SearchManager.QUERY, query);
353 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
356 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
358 i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
359 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
361 startActivity(Intent.createChooser(i, title));
365 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
366 public void onStartTrackingTouch(SeekBar bar) {
367 mLastSeekEventTime = 0;
370 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
371 if (!fromuser || (mService == null)) return;
372 long now = SystemClock.elapsedRealtime();
373 if ((now - mLastSeekEventTime) > 250) {
374 mLastSeekEventTime = now;
375 mPosOverride = mDuration * progress / 1000;
377 mService.seek(mPosOverride);
378 } catch (RemoteException ex) {
381 // trackball event, allow progress updates
388 public void onStopTrackingTouch(SeekBar bar) {
394 private View.OnClickListener mQueueListener = new View.OnClickListener() {
395 public void onClick(View v) {
397 new Intent(Intent.ACTION_EDIT)
398 .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
399 .putExtra("playlist", "nowplaying")
404 private View.OnClickListener mShuffleListener = new View.OnClickListener() {
405 public void onClick(View v) {
410 private View.OnClickListener mRepeatListener = new View.OnClickListener() {
411 public void onClick(View v) {
416 private View.OnClickListener mPauseListener = new View.OnClickListener() {
417 public void onClick(View v) {
422 private View.OnClickListener mPrevListener = new View.OnClickListener() {
423 public void onClick(View v) {
424 if (mService == null) return;
426 if (mService.position() < 2000) {
432 } catch (RemoteException ex) {
437 private View.OnClickListener mNextListener = new View.OnClickListener() {
438 public void onClick(View v) {
439 if (mService == null) return;
442 } catch (RemoteException ex) {
447 private RepeatingImageButton.RepeatListener mRewListener =
448 new RepeatingImageButton.RepeatListener() {
449 public void onRepeat(View v, long howlong, int repcnt) {
450 scanBackward(repcnt, howlong);
454 private RepeatingImageButton.RepeatListener mFfwdListener =
455 new RepeatingImageButton.RepeatListener() {
456 public void onRepeat(View v, long howlong, int repcnt) {
457 scanForward(repcnt, howlong);
462 public void onStop() {
464 if (mService != null && mOneShot && getChangingConfigurations() == 0) {
467 } catch (RemoteException ex) {
470 mHandler.removeMessages(REFRESH);
471 unregisterReceiver(mStatusListener);
472 MusicUtils.unbindFromService(this);
478 public void onSaveInstanceState(Bundle outState) {
479 outState.putBoolean("oneshot", mOneShot);
480 super.onSaveInstanceState(outState);
484 public void onStart() {
488 if (false == MusicUtils.bindToService(this, osc)) {
489 // something went wrong
490 mHandler.sendEmptyMessage(QUIT);
493 IntentFilter f = new IntentFilter();
494 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
495 f.addAction(MediaPlaybackService.META_CHANGED);
496 f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
497 registerReceiver(mStatusListener, new IntentFilter(f));
499 long next = refreshNow();
500 queueNextRefresh(next);
504 public void onNewIntent(Intent intent) {
506 mOneShot = intent.getBooleanExtra("oneshot", false);
510 public void onResume() {
513 setPauseButtonImage();
517 public void onDestroy()
519 mAlbumArtWorker.quit();
521 //System.out.println("***************** playback activity onDestroy\n");
525 public boolean onCreateOptionsMenu(Menu menu) {
526 super.onCreateOptionsMenu(menu);
527 // Don't show the menu items if we got launched by path/filedescriptor, or
528 // if we're in one shot mode. In most cases, these menu items are not
529 // useful in those modes, so for consistency we never show them in these
530 // modes, instead of tailoring them to the specific file being played.
531 if (MusicUtils.getCurrentAudioId() >= 0 && !mOneShot) {
532 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
533 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
534 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
535 R.string.add_to_playlist).setIcon(android.R.drawable.ic_menu_add);
536 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
537 menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
544 public boolean onPrepareOptionsMenu(Menu menu) {
545 MenuItem item = menu.findItem(PARTY_SHUFFLE);
547 int shuffle = MusicUtils.getCurrentShuffleMode();
548 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
549 item.setIcon(R.drawable.ic_menu_party_shuffle);
550 item.setTitle(R.string.party_shuffle_off);
552 item.setIcon(R.drawable.ic_menu_party_shuffle);
553 item.setTitle(R.string.party_shuffle);
556 item = menu.findItem(ADD_TO_PLAYLIST);
558 SubMenu sub = item.getSubMenu();
559 MusicUtils.makePlaylistMenu(this, sub);
565 public boolean onOptionsItemSelected(MenuItem item) {
568 switch (item.getItemId()) {
570 intent = new Intent();
571 intent.setClass(this, MusicBrowserActivity.class);
572 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
573 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
574 startActivity(intent);
576 case USE_AS_RINGTONE: {
577 // Set the system setting to make this the current ringtone
578 if (mService != null) {
579 MusicUtils.setRingtone(this, mService.getAudioId());
584 if (mService != null) {
585 int shuffle = mService.getShuffleMode();
586 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
587 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
589 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
592 setShuffleButtonImage();
596 intent = new Intent();
597 intent.setClass(this, CreatePlaylist.class);
598 startActivityForResult(intent, NEW_PLAYLIST);
602 case PLAYLIST_SELECTED: {
603 long [] list = new long[1];
604 list[0] = MusicUtils.getCurrentAudioId();
605 long playlist = item.getIntent().getLongExtra("playlist", 0);
606 MusicUtils.addToPlaylist(this, list, playlist);
611 if (mService != null) {
612 long [] list = new long[1];
613 list[0] = MusicUtils.getCurrentAudioId();
614 Bundle b = new Bundle();
615 b.putString("description", getString(R.string.delete_song_desc,
616 mService.getTrackName()));
617 b.putLongArray("items", list);
618 intent = new Intent();
619 intent.setClass(this, DeleteItems.class);
621 startActivityForResult(intent, -1);
626 } catch (RemoteException ex) {
628 return super.onOptionsItemSelected(item);
632 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
633 if (resultCode != RESULT_OK) {
636 switch (requestCode) {
638 Uri uri = intent.getData();
640 long [] list = new long[1];
641 list[0] = MusicUtils.getCurrentAudioId();
642 int playlist = Integer.parseInt(uri.getLastPathSegment());
643 MusicUtils.addToPlaylist(this, list, playlist);
648 private final int keyboard[][] = {
671 KeyEvent.KEYCODE_DEL,
681 KeyEvent.KEYCODE_COMMA,
682 KeyEvent.KEYCODE_PERIOD,
683 KeyEvent.KEYCODE_ENTER
691 private boolean seekMethod1(int keyCode)
693 for(int x=0;x<10;x++) {
694 for(int y=0;y<3;y++) {
695 if(keyboard[y][x] == keyCode) {
698 if(x == lastX && y == lastY) dir = 0;
699 else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
700 else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
702 else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
703 else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
705 else if (y < lastY && x <= 4) dir = 1;
706 else if (y < lastY && x >= 5) dir = -1;
708 else if (y > lastY && x <= 4) dir = -1;
709 else if (y > lastY && x >= 5) dir = 1;
713 mService.seek(mService.position() + dir * 5);
714 } catch (RemoteException ex) {
726 private boolean seekMethod2(int keyCode)
728 if (mService == null) return false;
729 for(int i=0;i<10;i++) {
730 if(keyboard[0][i] == keyCode) {
731 int seekpercentage = 100*i/10;
733 mService.seek(mService.duration() * seekpercentage / 100);
734 } catch (RemoteException ex) {
744 public boolean onKeyUp(int keyCode, KeyEvent event) {
748 case KeyEvent.KEYCODE_DPAD_LEFT:
749 if (!useDpadMusicControl()) {
752 if (mService != null) {
753 if (!mSeeking && mStartSeekPos >= 0) {
754 mPauseButton.requestFocus();
755 if (mStartSeekPos < 1000) {
761 scanBackward(-1, event.getEventTime() - event.getDownTime());
762 mPauseButton.requestFocus();
769 case KeyEvent.KEYCODE_DPAD_RIGHT:
770 if (!useDpadMusicControl()) {
773 if (mService != null) {
774 if (!mSeeking && mStartSeekPos >= 0) {
775 mPauseButton.requestFocus();
778 scanForward(-1, event.getEventTime() - event.getDownTime());
779 mPauseButton.requestFocus();
787 } catch (RemoteException ex) {
789 return super.onKeyUp(keyCode, event);
792 private boolean useDpadMusicControl() {
793 if (mDeviceHasDpad && (mPrevButton.isFocused() ||
794 mNextButton.isFocused() ||
795 mPauseButton.isFocused())) {
802 public boolean onKeyDown(int keyCode, KeyEvent event)
805 int repcnt = event.getRepeatCount();
807 if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
814 case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
815 case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
817 case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
818 case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
819 case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
820 case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
822 case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
823 case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
825 case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
826 case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
827 case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
828 case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
832 case KeyEvent.KEYCODE_SLASH:
833 seekmethod = 1 - seekmethod;
836 case KeyEvent.KEYCODE_DPAD_LEFT:
837 if (!useDpadMusicControl()) {
840 if (!mPrevButton.hasFocus()) {
841 mPrevButton.requestFocus();
843 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
845 case KeyEvent.KEYCODE_DPAD_RIGHT:
846 if (!useDpadMusicControl()) {
849 if (!mNextButton.hasFocus()) {
850 mNextButton.requestFocus();
852 scanForward(repcnt, event.getEventTime() - event.getDownTime());
855 case KeyEvent.KEYCODE_S:
859 case KeyEvent.KEYCODE_DPAD_CENTER:
860 case KeyEvent.KEYCODE_SPACE:
864 return super.onKeyDown(keyCode, event);
867 private void scanBackward(int repcnt, long delta) {
868 if(mService == null) return;
871 mStartSeekPos = mService.position();
872 mLastSeekEventTime = 0;
877 // seek at 10x speed for the first 5 seconds
880 // seek at 40x after that
881 delta = 50000 + (delta - 5000) * 40;
883 long newpos = mStartSeekPos - delta;
885 // move to previous track
887 long duration = mService.duration();
888 mStartSeekPos += duration;
891 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
892 mService.seek(newpos);
893 mLastSeekEventTime = delta;
896 mPosOverride = newpos;
902 } catch (RemoteException ex) {
906 private void scanForward(int repcnt, long delta) {
907 if(mService == null) return;
910 mStartSeekPos = mService.position();
911 mLastSeekEventTime = 0;
916 // seek at 10x speed for the first 5 seconds
919 // seek at 40x after that
920 delta = 50000 + (delta - 5000) * 40;
922 long newpos = mStartSeekPos + delta;
923 long duration = mService.duration();
924 if (newpos >= duration) {
925 // move to next track
927 mStartSeekPos -= duration; // is OK to go negative
930 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
931 mService.seek(newpos);
932 mLastSeekEventTime = delta;
935 mPosOverride = newpos;
941 } catch (RemoteException ex) {
945 private void doPauseResume() {
947 if(mService != null) {
948 if (mService.isPlaying()) {
954 setPauseButtonImage();
956 } catch (RemoteException ex) {
960 private void toggleShuffle() {
961 if (mService == null) {
965 int shuffle = mService.getShuffleMode();
966 if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
967 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
968 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
969 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
970 setRepeatButtonImage();
972 showToast(R.string.shuffle_on_notif);
973 } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
974 shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
975 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
976 showToast(R.string.shuffle_off_notif);
978 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
980 setShuffleButtonImage();
981 } catch (RemoteException ex) {
985 private void cycleRepeat() {
986 if (mService == null) {
990 int mode = mService.getRepeatMode();
991 if (mode == MediaPlaybackService.REPEAT_NONE) {
992 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
993 showToast(R.string.repeat_all_notif);
994 } else if (mode == MediaPlaybackService.REPEAT_ALL) {
995 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
996 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
997 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
998 setShuffleButtonImage();
1000 showToast(R.string.repeat_current_notif);
1002 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
1003 showToast(R.string.repeat_off_notif);
1005 setRepeatButtonImage();
1006 } catch (RemoteException ex) {
1011 private void showToast(int resid) {
1012 if (mToast == null) {
1013 mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
1015 mToast.setText(resid);
1019 private void startPlayback() {
1021 if(mService == null)
1023 Intent intent = getIntent();
1024 String filename = "";
1025 Uri uri = intent.getData();
1026 if (uri != null && uri.toString().length() > 0) {
1027 // If this is a file:// URI, just use the path directly instead
1028 // of going through the open-from-filedescriptor codepath.
1029 String scheme = uri.getScheme();
1030 if ("file".equals(scheme)) {
1031 filename = uri.getPath();
1033 filename = uri.toString();
1036 if (! ContentResolver.SCHEME_CONTENT.equals(scheme) ||
1037 ! MediaStore.AUTHORITY.equals(uri.getAuthority())) {
1041 mService.openFile(filename, mOneShot);
1043 setIntent(new Intent());
1044 } catch (Exception ex) {
1045 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
1050 long next = refreshNow();
1051 queueNextRefresh(next);
1054 private ServiceConnection osc = new ServiceConnection() {
1055 public void onServiceConnected(ComponentName classname, IBinder obj) {
1056 mService = IMediaPlaybackService.Stub.asInterface(obj);
1059 // Assume something is playing when the service says it is,
1060 // but also if the audio ID is valid but the service is paused.
1061 if (mService.getAudioId() >= 0 || mService.isPlaying() ||
1062 mService.getPath() != null) {
1063 // something is playing now, we're done
1064 if (mOneShot || mService.getAudioId() < 0) {
1065 mRepeatButton.setVisibility(View.INVISIBLE);
1066 mShuffleButton.setVisibility(View.INVISIBLE);
1067 mQueueButton.setVisibility(View.INVISIBLE);
1069 mRepeatButton.setVisibility(View.VISIBLE);
1070 mShuffleButton.setVisibility(View.VISIBLE);
1071 mQueueButton.setVisibility(View.VISIBLE);
1072 setRepeatButtonImage();
1073 setShuffleButtonImage();
1075 setPauseButtonImage();
1078 } catch (RemoteException ex) {
1080 // Service is dead or not playing anything. If we got here as part
1081 // of a "play this file" Intent, exit. Otherwise go to the Music
1082 // app start screen.
1083 if (getIntent().getData() == null) {
1084 Intent intent = new Intent(Intent.ACTION_MAIN);
1085 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1086 intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
1087 startActivity(intent);
1091 public void onServiceDisconnected(ComponentName classname) {
1095 private void setRepeatButtonImage() {
1097 switch (mService.getRepeatMode()) {
1098 case MediaPlaybackService.REPEAT_ALL:
1099 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
1101 case MediaPlaybackService.REPEAT_CURRENT:
1102 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
1105 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
1108 } catch (RemoteException ex) {
1112 private void setShuffleButtonImage() {
1114 switch (mService.getShuffleMode()) {
1115 case MediaPlaybackService.SHUFFLE_NONE:
1116 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
1118 case MediaPlaybackService.SHUFFLE_AUTO:
1119 mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
1122 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
1125 } catch (RemoteException ex) {
1129 private void setPauseButtonImage() {
1131 if (mService != null && mService.isPlaying()) {
1132 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
1134 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
1136 } catch (RemoteException ex) {
1140 private ImageView mAlbum;
1141 private TextView mCurrentTime;
1142 private TextView mTotalTime;
1143 private TextView mArtistName;
1144 private TextView mAlbumName;
1145 private TextView mTrackName;
1146 private ProgressBar mProgress;
1147 private long mPosOverride = -1;
1148 private boolean mFromTouch = false;
1149 private long mDuration;
1150 private int seekmethod;
1151 private boolean paused;
1153 private static final int REFRESH = 1;
1154 private static final int QUIT = 2;
1155 private static final int GET_ALBUM_ART = 3;
1156 private static final int ALBUM_ART_DECODED = 4;
1158 private void queueNextRefresh(long delay) {
1160 Message msg = mHandler.obtainMessage(REFRESH);
1161 mHandler.removeMessages(REFRESH);
1162 mHandler.sendMessageDelayed(msg, delay);
1166 private long refreshNow() {
1167 if(mService == null)
1170 long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
1171 long remaining = 1000 - (pos % 1000);
1172 if ((pos >= 0) && (mDuration > 0)) {
1173 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
1175 if (mService.isPlaying()) {
1176 mCurrentTime.setVisibility(View.VISIBLE);
1178 // blink the counter
1179 int vis = mCurrentTime.getVisibility();
1180 mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1184 mProgress.setProgress((int) (1000 * pos / mDuration));
1186 mCurrentTime.setText("--:--");
1187 mProgress.setProgress(1000);
1189 // return the number of milliseconds until the next full second, so
1190 // the counter can be updated at just the right time
1192 } catch (RemoteException ex) {
1197 private final Handler mHandler = new Handler() {
1199 public void handleMessage(Message msg) {
1201 case ALBUM_ART_DECODED:
1202 mAlbum.setImageBitmap((Bitmap)msg.obj);
1203 mAlbum.getDrawable().setDither(true);
1207 long next = refreshNow();
1208 queueNextRefresh(next);
1212 // This can be moved back to onCreate once the bug that prevents
1213 // Dialogs from being started from onCreate/onResume is fixed.
1214 new AlertDialog.Builder(MediaPlaybackActivity.this)
1215 .setTitle(R.string.service_start_error_title)
1216 .setMessage(R.string.service_start_error_msg)
1217 .setPositiveButton(R.string.service_start_error_button,
1218 new DialogInterface.OnClickListener() {
1219 public void onClick(DialogInterface dialog, int whichButton) {
1223 .setCancelable(false)
1233 private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1235 public void onReceive(Context context, Intent intent) {
1236 String action = intent.getAction();
1237 if (action.equals(MediaPlaybackService.META_CHANGED)) {
1238 // redraw the artist/title info and
1239 // set new max for progress bar
1241 setPauseButtonImage();
1242 queueNextRefresh(1);
1243 } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1247 setPauseButtonImage();
1249 } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1250 setPauseButtonImage();
1255 private static class AlbumSongIdWrapper {
1256 public long albumid;
1258 AlbumSongIdWrapper(long aid, long sid) {
1264 private void updateTrackInfo() {
1265 if (mService == null) {
1269 String path = mService.getPath();
1275 long songid = mService.getAudioId();
1276 if (songid < 0 && path.toLowerCase().startsWith("http://")) {
1277 // Once we can get album art and meta data from MediaPlayer, we
1278 // can show that info again when streaming.
1279 ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
1280 ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
1281 mAlbum.setVisibility(View.GONE);
1282 mTrackName.setText(path);
1283 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1284 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(-1, -1)).sendToTarget();
1286 ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
1287 ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
1288 String artistName = mService.getArtistName();
1289 if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1290 artistName = getString(R.string.unknown_artist_name);
1292 mArtistName.setText(artistName);
1293 String albumName = mService.getAlbumName();
1294 long albumid = mService.getAlbumId();
1295 if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
1296 albumName = getString(R.string.unknown_album_name);
1299 mAlbumName.setText(albumName);
1300 mTrackName.setText(mService.getTrackName());
1301 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1302 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(albumid, songid)).sendToTarget();
1303 mAlbum.setVisibility(View.VISIBLE);
1305 mDuration = mService.duration();
1306 mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1307 } catch (RemoteException ex) {
1312 public class AlbumArtHandler extends Handler {
1313 private long mAlbumId = -1;
1315 public AlbumArtHandler(Looper looper) {
1319 public void handleMessage(Message msg)
1321 long albumid = ((AlbumSongIdWrapper) msg.obj).albumid;
1322 long songid = ((AlbumSongIdWrapper) msg.obj).songid;
1323 if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1324 // while decoding the new image, show the default album art
1325 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1326 mHandler.removeMessages(ALBUM_ART_DECODED);
1327 mHandler.sendMessageDelayed(numsg, 300);
1328 Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, albumid);
1330 bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, -1);
1334 numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1335 mHandler.removeMessages(ALBUM_ART_DECODED);
1336 mHandler.sendMessage(numsg);
1343 private static class Worker implements Runnable {
1344 private final Object mLock = new Object();
1345 private Looper mLooper;
1348 * Creates a worker thread with the given name. The thread
1349 * then runs a {@link android.os.Looper}.
1350 * @param name A name for the new thread
1352 Worker(String name) {
1353 Thread t = new Thread(null, this, name);
1354 t.setPriority(Thread.MIN_PRIORITY);
1356 synchronized (mLock) {
1357 while (mLooper == null) {
1360 } catch (InterruptedException ex) {
1366 public Looper getLooper() {
1371 synchronized (mLock) {
1373 mLooper = Looper.myLooper();
1379 public void quit() {