OSDN Git Service

Initial Contribution
[android-x86/packages-apps-Music.git] / src / com / android / music / MediaPlaybackActivity.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.music;
18
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;
58
59
60 public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs, View.OnTouchListener
61 {
62     private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
63     
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;
78     private Toast mToast;
79     private boolean mRelaunchAfterConfigChange;
80
81     public MediaPlaybackActivity()
82     {
83     }
84
85     /** Called when the activity is first created. */
86     @Override
87     public void onCreate(Bundle icicle)
88     {
89         super.onCreate(icicle);
90         setVolumeControlStream(AudioManager.STREAM_MUSIC);
91
92         mAlbumArtWorker = new Worker("album art worker");
93         mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
94
95         requestWindowFeature(Window.FEATURE_NO_TITLE);
96         setContentView(R.layout.audio_player);
97
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);
105
106         View v = (View)mArtistName.getParent(); 
107         v.setOnTouchListener(this);
108         registerForContextMenu(v);
109
110         v = (View)mAlbumName.getParent();
111         v.setOnTouchListener(this);
112         registerForContextMenu(v);
113
114         v = (View)mTrackName.getParent();
115         v.setOnTouchListener(this);
116         registerForContextMenu(v);
117         
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);
127         seekmethod = 1;
128
129         mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation == 
130             Resources.Configuration.NAVIGATION_TRACKBALL);*/
131         
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);
138         
139         if (mProgress instanceof SeekBar) {
140             SeekBar seeker = (SeekBar) mProgress;
141             seeker.setOnSeekBarChangeListener(mSeekListener);
142         }
143         mProgress.setMax(1000);
144         
145         if (icicle != null) {
146             mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
147             mOneShot = icicle.getBoolean("oneshot");
148         } else {
149             mOneShot = getIntent().getBooleanExtra("oneshot", false);
150         }
151     }
152     
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);
160         }
161         return false; 
162     }
163
164     @Override
165     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
166
167         /*
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.
172          */
173         CharSequence title = null;
174         String query = 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) {
179             title = artist;
180             query = artist.toString();
181         } else if (view.equals(mAlbumName.getParent()) &&
182                 artist.length() > 0 && album.length() > 0) {
183             title = album ;
184             query = artist.toString() + " " + album.toString();
185         } else if (view.equals(mTrackName.getParent()) &&
186                 artist.length() > 0 && song.length() > 0) {
187             title = song;
188             query = artist.toString() + " " + song.toString();
189         } else {
190             return;
191         }
192         
193         title = getString(R.string.mediasearch, title);
194         TextView tv = new TextView(this);
195         tv.setText(title);
196         tv.setTextSize(18);
197         tv.setPadding(8, 8, 8, 8);
198         menu.setHeaderView(tv);
199         //menu.setHeaderTitle(title);
200         
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);
207         if ( ai != null) {
208             menu.add(R.string.mediasearch_amazon).setIntent(i);
209         }
210         
211         i = new Intent();
212         i.setAction(Intent.ACTION_WEB_SEARCH);
213         i.putExtra("query", query);
214         menu.add(R.string.mediasearch_google).setIntent(i);
215
216         i = new Intent();
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);
221         return;
222     }
223
224     private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
225         public void onStartTrackingTouch(SeekBar bar) {
226             mLastSeekEventTime = 0;
227         }
228         public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) {
229             if (mService == null) return;
230             if (fromtouch) {
231                 long now = SystemClock.elapsedRealtime();
232                 if ((now - mLastSeekEventTime) > 250) {
233                     mLastSeekEventTime = now;
234                     mPosOverride = mDuration * progress / 1000;
235                     try {
236                         mService.seek(mPosOverride);
237                     } catch (RemoteException ex) {
238                     }
239                 }
240             }
241         }
242         public void onStopTrackingTouch(SeekBar bar) {
243             mPosOverride = -1;
244         }
245     };
246     
247     private View.OnClickListener mQueueListener = new View.OnClickListener() {
248         public void onClick(View v) {
249             startActivity(
250                     new Intent(Intent.ACTION_EDIT)
251                     .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
252                     .putExtra("playlist", "nowplaying")
253             );
254         }
255     };
256     
257     private View.OnClickListener mShuffleListener = new View.OnClickListener() {
258         public void onClick(View v) {
259             toggleShuffle();
260         }
261     };
262
263     private View.OnClickListener mRepeatListener = new View.OnClickListener() {
264         public void onClick(View v) {
265             cycleRepeat();
266         }
267     };
268
269     private View.OnClickListener mPauseListener = new View.OnClickListener() {
270         public void onClick(View v) {
271             doPauseResume();
272         }
273     };
274
275     private View.OnClickListener mPrevListener = new View.OnClickListener() {
276         public void onClick(View v) {
277             if (mService == null) return;
278             try {
279                 if (mService.position() < 2000) {
280                     mService.prev();
281                 } else {
282                     mService.seek(0);
283                     mService.play();
284                 }
285             } catch (RemoteException ex) {
286             }
287         }
288     };
289
290     private View.OnClickListener mNextListener = new View.OnClickListener() {
291         public void onClick(View v) {
292             if (mService == null) return;
293             try {
294                 mService.next();
295             } catch (RemoteException ex) {
296             }
297         }
298     };
299
300     private RepeatingImageButton.RepeatListener mRewListener =
301         new RepeatingImageButton.RepeatListener() {
302         public void onRepeat(View v, long howlong, int repcnt) {
303             scanBackward(repcnt, howlong);
304         }
305     };
306     
307     private RepeatingImageButton.RepeatListener mFfwdListener =
308         new RepeatingImageButton.RepeatListener() {
309         public void onRepeat(View v, long howlong, int repcnt) {
310             scanForward(repcnt, howlong);
311         }
312     };
313    
314     @Override
315     public void onStop() {
316         paused = true;
317         if (mService != null && mOneShot && getChangingConfigurations() == 0) {
318             try {
319                 mService.stop();
320             } catch (RemoteException ex) {
321             }
322         }
323         mHandler.removeMessages(REFRESH);
324         unregisterReceiver(mStatusListener);
325         MusicUtils.unbindFromService(this);
326         super.onStop();
327     }
328
329     @Override
330     public void onSaveInstanceState(Bundle outState) {
331         outState.putBoolean("configchange", getChangingConfigurations() != 0);
332         outState.putBoolean("oneshot", mOneShot);
333         super.onSaveInstanceState(outState);
334     }
335     
336     @Override
337     public void onStart() {
338         super.onStart();
339         paused = false;
340
341         if (false == MusicUtils.bindToService(this, osc)) {
342             // something went wrong
343             mHandler.sendEmptyMessage(QUIT);
344         }
345         
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));
351         updateTrackInfo();
352         long next = refreshNow();
353         queueNextRefresh(next);
354     }
355     
356     @Override
357     public void onNewIntent(Intent intent) {
358         setIntent(intent);
359         mOneShot = intent.getBooleanExtra("oneshot", false);
360     }
361     
362     @Override
363     public void onResume() {
364         super.onResume();
365         updateTrackInfo();
366         setPauseButtonImage();
367     }
368     
369     @Override
370     public void onDestroy()
371     {
372         mAlbumArtWorker.quit();
373         super.onDestroy();
374         //System.out.println("***************** playback activity onDestroy\n");
375     }
376
377     @Override
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) {
383             if (!mOneShot) {
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()
386             }
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);
392         }
393         return true;
394     }
395
396     @Override
397     public boolean onPrepareOptionsMenu(Menu menu) {
398         MenuItem item = menu.findItem(PARTY_SHUFFLE);
399         if (item != null) {
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);
404             } else {
405                 item.setIcon(R.drawable.ic_menu_party_shuffle);
406                 item.setTitle(R.string.party_shuffle);
407             }
408         }
409         return true;
410     }
411
412     @Override
413     public boolean onOptionsItemSelected(MenuItem item) {
414         Intent intent;
415         try {
416             switch (item.getItemId()) {
417                 case GOTO_START:
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);
423                     break;
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());
428                     }
429                     return true;
430                 }
431                 case PARTY_SHUFFLE:
432                     if (mService != null) {
433                         int shuffle = mService.getShuffleMode();
434                         if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
435                             mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
436                         } else {
437                             mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
438                         }
439                     }
440                     setShuffleButtonImage();
441                     break;
442                     
443                 case NEW_PLAYLIST: {
444                     intent = new Intent();
445                     intent.setClass(this, CreatePlaylist.class);
446                     startActivityForResult(intent, NEW_PLAYLIST);
447                     return true;
448                 }
449
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);
455                     return true;
456                 }
457                 
458                 case DELETE_ITEM: {
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);
467                         intent.putExtras(b);
468                         startActivityForResult(intent, -1);
469                     }
470                     return true;
471                 }
472             }
473         } catch (RemoteException ex) {
474         }
475         return super.onOptionsItemSelected(item);
476     }
477     
478     @Override
479     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
480         if (resultCode != RESULT_OK) {
481             return;
482         }
483         switch (requestCode) {
484             case NEW_PLAYLIST:
485                 Uri uri = Uri.parse(intent.getAction());
486                 if (uri != null) {
487                     int [] list = new int[1];
488                     list[0] = MusicUtils.getCurrentAudioId();
489                     int playlist = Integer.parseInt(uri.getLastPathSegment());
490                     MusicUtils.addToPlaylist(this, list, playlist);
491                 }
492                 break;
493         }
494     }
495     private final int keyboard[][] = {
496         {
497             KeyEvent.KEYCODE_Q,
498             KeyEvent.KEYCODE_W,
499             KeyEvent.KEYCODE_E,
500             KeyEvent.KEYCODE_R,
501             KeyEvent.KEYCODE_T,
502             KeyEvent.KEYCODE_Y,
503             KeyEvent.KEYCODE_U,
504             KeyEvent.KEYCODE_I,
505             KeyEvent.KEYCODE_O,
506             KeyEvent.KEYCODE_P,
507         },
508         {
509             KeyEvent.KEYCODE_A,
510             KeyEvent.KEYCODE_S,
511             KeyEvent.KEYCODE_D,
512             KeyEvent.KEYCODE_F,
513             KeyEvent.KEYCODE_G,
514             KeyEvent.KEYCODE_H,
515             KeyEvent.KEYCODE_J,
516             KeyEvent.KEYCODE_K,
517             KeyEvent.KEYCODE_L,
518             KeyEvent.KEYCODE_DEL,
519         },
520         {
521             KeyEvent.KEYCODE_Z,
522             KeyEvent.KEYCODE_X,
523             KeyEvent.KEYCODE_C,
524             KeyEvent.KEYCODE_V,
525             KeyEvent.KEYCODE_B,
526             KeyEvent.KEYCODE_N,
527             KeyEvent.KEYCODE_M,
528             KeyEvent.KEYCODE_COMMA,
529             KeyEvent.KEYCODE_PERIOD,
530             KeyEvent.KEYCODE_ENTER
531         }
532
533     };
534
535     private int lastX;
536     private int lastY;
537
538     private boolean seekMethod1(int keyCode)
539     {
540         for(int x=0;x<10;x++) {
541             for(int y=0;y<3;y++) {
542                 if(keyboard[y][x] == keyCode) {
543                     int dir = 0;
544                     // top row
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;
548                     // bottom row
549                     else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
550                     else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
551                     // moving up
552                     else if (y < lastY && x <= 4) dir = 1; 
553                     else if (y < lastY && x >= 5) dir = -1; 
554                     // moving down
555                     else if (y > lastY && x <= 4) dir = -1; 
556                     else if (y > lastY && x >= 5) dir = 1; 
557                     lastX = x;
558                     lastY = y;
559                     try {
560                         mService.seek(mService.position() + dir * 5);
561                     } catch (RemoteException ex) {
562                     }
563                     refreshNow();
564                     return true;
565                 }
566             }
567         }
568         lastX = -1;
569         lastY = -1;
570         return false;
571     }
572
573     private boolean seekMethod2(int keyCode)
574     {
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;
579                 try {
580                     mService.seek(mService.duration() * seekpercentage / 100);
581                 } catch (RemoteException ex) {
582                 }
583                 refreshNow();
584                 return true;
585             }
586         }
587         return false;
588     }
589
590     @Override
591     public boolean onKeyUp(int keyCode, KeyEvent event) {
592         try {
593             switch(keyCode)
594             {
595                 case KeyEvent.KEYCODE_DPAD_LEFT:
596                     if (mTrackball) {
597                         break;
598                     }
599                     if (mService != null) {
600                         if (!mSeeking && mStartSeekPos >= 0) {
601                             mPauseButton.requestFocus();
602                             if (mStartSeekPos < 1000) {
603                                 mService.prev();
604                             } else {
605                                 mService.seek(0);
606                             }
607                         } else {
608                             scanBackward(-1, event.getEventTime() - event.getDownTime());
609                             mPauseButton.requestFocus();
610                             mStartSeekPos = -1;
611                         }
612                     }
613                     mSeeking = false;
614                     mPosOverride = -1;
615                     return true;
616                 case KeyEvent.KEYCODE_DPAD_RIGHT:
617                     if (mTrackball) {
618                         break;
619                     }
620                     if (mService != null) {
621                         if (!mSeeking && mStartSeekPos >= 0) {
622                             mPauseButton.requestFocus();
623                             mService.next();
624                         } else {
625                             scanForward(-1, event.getEventTime() - event.getDownTime());
626                             mPauseButton.requestFocus();
627                             mStartSeekPos = -1;
628                         }
629                     }
630                     mSeeking = false;
631                     mPosOverride = -1;
632                     return true;
633             }
634         } catch (RemoteException ex) {
635         }
636         return super.onKeyUp(keyCode, event);
637     }
638
639     @Override
640     public boolean onKeyDown(int keyCode, KeyEvent event)
641     {
642         int direction = -1;
643         int repcnt = event.getRepeatCount();
644
645         if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
646             return true;
647
648         switch(keyCode)
649         {
650 /*
651             // image scale
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;
654             // image translate
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;
659             // camera rotation
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;
662             // camera translate
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;
667
668 */
669
670             case KeyEvent.KEYCODE_SLASH:
671                 seekmethod = 1 - seekmethod;
672                 return true;
673
674             case KeyEvent.KEYCODE_DPAD_LEFT:
675                 if (mTrackball) {
676                     break;
677                 }
678                 if (!mPrevButton.hasFocus()) {
679                     mPrevButton.requestFocus();
680                 }
681                 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
682                 return true;
683             case KeyEvent.KEYCODE_DPAD_RIGHT:
684                 if (mTrackball) {
685                     break;
686                 }
687                 if (!mNextButton.hasFocus()) {
688                     mNextButton.requestFocus();
689                 }
690                 scanForward(repcnt, event.getEventTime() - event.getDownTime());
691                 return true;
692
693             case KeyEvent.KEYCODE_S:
694                 toggleShuffle();
695                 return true;
696
697             case KeyEvent.KEYCODE_DPAD_CENTER:
698             case KeyEvent.KEYCODE_SPACE:
699                 doPauseResume();
700                 return true;
701         }
702         return super.onKeyDown(keyCode, event);
703     }
704     
705     private void scanBackward(int repcnt, long delta) {
706         if(mService == null) return;
707         try {
708             if(repcnt == 0) {
709                 mStartSeekPos = mService.position();
710                 mLastSeekEventTime = 0;
711                 mSeeking = false;
712             } else {
713                 mSeeking = true;
714                 if (delta < 5000) {
715                     // seek at 10x speed for the first 5 seconds
716                     delta = delta * 10; 
717                 } else {
718                     // seek at 40x after that
719                     delta = 50000 + (delta - 5000) * 40;
720                 }
721                 long newpos = mStartSeekPos - delta;
722                 if (newpos < 0) {
723                     // move to previous track
724                     mService.prev();
725                     long duration = mService.duration();
726                     mStartSeekPos += duration;
727                     newpos += duration;
728                 }
729                 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
730                     mService.seek(newpos);
731                     mLastSeekEventTime = delta;
732                 }
733                 if (repcnt >= 0) {
734                     mPosOverride = newpos;
735                 } else {
736                     mPosOverride = -1;
737                 }
738                 refreshNow();
739             }
740         } catch (RemoteException ex) {
741         }
742     }
743
744     private void scanForward(int repcnt, long delta) {
745         if(mService == null) return;
746         try {
747             if(repcnt == 0) {
748                 mStartSeekPos = mService.position();
749                 mLastSeekEventTime = 0;
750                 mSeeking = false;
751             } else {
752                 mSeeking = true;
753                 if (delta < 5000) {
754                     // seek at 10x speed for the first 5 seconds
755                     delta = delta * 10; 
756                 } else {
757                     // seek at 40x after that
758                     delta = 50000 + (delta - 5000) * 40;
759                 }
760                 long newpos = mStartSeekPos + delta;
761                 long duration = mService.duration();
762                 if (newpos >= duration) {
763                     // move to next track
764                     mService.next();
765                     mStartSeekPos -= duration; // is OK to go negative
766                     newpos -= duration;
767                 }
768                 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
769                     mService.seek(newpos);
770                     mLastSeekEventTime = delta;
771                 }
772                 if (repcnt >= 0) {
773                     mPosOverride = newpos;
774                 } else {
775                     mPosOverride = -1;
776                 }
777                 refreshNow();
778             }
779         } catch (RemoteException ex) {
780         }
781     }
782     
783     private void doPauseResume() {
784         try {
785             if(mService != null) {
786                 if (mService.isPlaying()) {
787                     mService.pause();
788                 } else {
789                     mService.play();
790                 }
791                 refreshNow();
792                 setPauseButtonImage();
793             }
794         } catch (RemoteException ex) {
795         }
796     }
797     
798     private void toggleShuffle() {
799         if (mService == null) {
800             return;
801         }
802         try {
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();
809                 }
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);
815             } else {
816                 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
817             }
818             setShuffleButtonImage();
819         } catch (RemoteException ex) {
820         }
821     }
822     
823     private void cycleRepeat() {
824         if (mService == null) {
825             return;
826         }
827         try {
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();
837                 }
838                 showToast(R.string.repeat_current_notif);
839             } else {
840                 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
841                 showToast(R.string.repeat_off_notif);
842             }
843             setRepeatButtonImage();
844         } catch (RemoteException ex) {
845         }
846         
847     }
848     
849     private void showToast(int resid) {
850         if (mToast == null) {
851             mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
852         }
853         mToast.setText(resid);
854         mToast.show();
855     }
856
857     private void startPlayback() {
858
859         if(mService == null)
860             return;
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();
870             } else {
871                 filename = uri.toString();
872             }
873             try {
874                 mOneShot = true;
875                 if (! mRelaunchAfterConfigChange) {
876                     mService.stop();
877                     mService.openfile(filename);
878                     mService.play();
879                 }
880             } catch (Exception ex) {
881                 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
882             }
883         }
884
885         updateTrackInfo();
886         long next = refreshNow();
887         queueNextRefresh(next);
888     }
889
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;
895                 }
896                 startPlayback();
897                 try {
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
903                         if (mOneShot) {
904                             mRepeatButton.setVisibility(View.INVISIBLE);
905                             mShuffleButton.setVisibility(View.INVISIBLE);
906                             mQueueButton.setVisibility(View.INVISIBLE);
907                         } else {
908                             mRepeatButton.setVisibility(View.VISIBLE);
909                             mShuffleButton.setVisibility(View.VISIBLE);
910                             mQueueButton.setVisibility(View.VISIBLE);
911                             setRepeatButtonImage();
912                             setShuffleButtonImage();
913                         }
914                         setPauseButtonImage();
915                         return;
916                     }
917                 } catch (RemoteException ex) {
918                 }
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
921                 // app start screen.
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);
927                 }
928                 finish();
929             }
930             public void onServiceDisconnected(ComponentName classname) {
931             }
932     };
933
934     private void setRepeatButtonImage() {
935         try {
936             switch (mService.getRepeatMode()) {
937                 case MediaPlaybackService.REPEAT_ALL:
938                     mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
939                     break;
940                 case MediaPlaybackService.REPEAT_CURRENT:
941                     mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
942                     break;
943                 default:
944                     mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
945                     break;
946             }
947         } catch (RemoteException ex) {
948         }
949     }
950     
951     private void setShuffleButtonImage() {
952         try {
953             switch (mService.getShuffleMode()) {
954                 case MediaPlaybackService.SHUFFLE_NONE:
955                     mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
956                     break;
957                 case MediaPlaybackService.SHUFFLE_AUTO:
958                     mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
959                     break;
960                 default:
961                     mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
962                     break;
963             }
964         } catch (RemoteException ex) {
965         }
966     }
967     
968     private void setPauseButtonImage() {
969         try {
970             if (mService != null && mService.isPlaying()) {
971                 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
972             } else {
973                 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
974             }
975         } catch (RemoteException ex) {
976         }
977     }
978     
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;
990
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;
995
996     private void queueNextRefresh(long delay) {
997         if (!paused) {
998             Message msg = mHandler.obtainMessage(REFRESH);
999             mHandler.removeMessages(REFRESH);
1000             mHandler.sendMessageDelayed(msg, delay);
1001         }
1002     }
1003
1004     private long refreshNow() {
1005         if(mService == null)
1006             return 500;
1007         try {
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));
1012                 
1013                 if (mService.isPlaying()) {
1014                     mCurrentTime.setVisibility(View.VISIBLE);
1015                 } else {
1016                     // blink the counter
1017                     int vis = mCurrentTime.getVisibility();
1018                     mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1019                     remaining = 500;
1020                 }
1021
1022                 mProgress.setProgress((int) (1000 * pos / mDuration));
1023             } else {
1024                 mCurrentTime.setText("--:--");
1025                 mProgress.setProgress(1000);
1026             }
1027             // return the number of milliseconds until the next full second, so
1028             // the counter can be updated at just the right time
1029             return remaining;
1030         } catch (RemoteException ex) {
1031         }
1032         return 500;
1033     }
1034     
1035     private final Handler mHandler = new Handler() {
1036         @Override
1037         public void handleMessage(Message msg) {
1038             switch (msg.what) {
1039                 case ALBUM_ART_DECODED:
1040                     mAlbum.setArtwork((Bitmap)msg.obj);
1041                     mAlbum.invalidate();
1042                     break;
1043
1044                 case REFRESH:
1045                     long next = refreshNow();
1046                     queueNextRefresh(next);
1047                     break;
1048                     
1049                 case QUIT:
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) {
1058                                             finish();
1059                                         }
1060                                     })
1061                             .setCancelable(false)
1062                             .show();
1063                     break;
1064
1065                 default:
1066                     break;
1067             }
1068         }
1069     };
1070
1071     private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1072         @Override
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
1078                 updateTrackInfo();
1079                 setPauseButtonImage();
1080                 queueNextRefresh(1);
1081             } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1082                 if (mOneShot) {
1083                     finish();
1084                 } else {
1085                     setPauseButtonImage();
1086                 }
1087             } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1088                 setPauseButtonImage();
1089             }
1090         }
1091     };
1092
1093     private void updateTrackInfo() {
1094         if (mService == null) {
1095             return;
1096         }
1097         try {
1098             if (mService.getPath() == null) {
1099                 finish();
1100                 return;
1101             }
1102             String artistName = mService.getArtistName();
1103             if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1104                 artistName = getString(R.string.unknown_artist_name);
1105             }
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);
1111                 albumid = -1;
1112             }
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) {
1120             finish();
1121         }
1122     }
1123     
1124     public class AlbumArtHandler extends Handler {
1125         private int mAlbumId = -1;
1126         
1127         public AlbumArtHandler(Looper looper) {
1128             super(looper);
1129         }
1130         public void handleMessage(Message msg)
1131         {
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);
1139                 if (bm == null) {
1140                     bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
1141                     albumid = -1;
1142                 }
1143                 if (bm != null) {
1144                     numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1145                     mHandler.removeMessages(ALBUM_ART_DECODED);
1146                     mHandler.sendMessage(numsg);
1147                 }
1148                 mAlbumId = albumid;
1149             }
1150         }
1151     }
1152     
1153     private class Worker implements Runnable {
1154         private final Object mLock = new Object();
1155         private Looper mLooper;
1156         
1157         /**
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
1161          */
1162         Worker(String name) {
1163             Thread t = new Thread(null, this, name);
1164             t.setPriority(Thread.MIN_PRIORITY);
1165             t.start();
1166             synchronized (mLock) {
1167                 while (mLooper == null) {
1168                     try {
1169                         mLock.wait();
1170                     } catch (InterruptedException ex) {
1171                     }
1172                 }
1173             }
1174         }
1175         
1176         public Looper getLooper() {
1177             return mLooper;
1178         }
1179         
1180         public void run() {
1181             synchronized (mLock) {
1182                 Looper.prepare();
1183                 mLooper = Looper.myLooper();
1184                 mLock.notifyAll();
1185             }
1186             Looper.loop();
1187         }
1188         
1189         public void quit() {
1190             mLooper.quit();
1191         }
1192     }
1193 }
1194