OSDN Git Service

Merge commit 'korg/cupcake'
[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.app.SearchManager;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29 import android.graphics.Bitmap;
30 import android.media.AudioManager;
31 import android.media.MediaFile;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.RemoteException;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.SystemClock;
40 import android.provider.MediaStore;
41 import android.text.Layout;
42 import android.text.TextUtils.TruncateAt;
43 import android.util.Log;
44 import android.view.KeyEvent;
45 import android.view.Menu;
46 import android.view.MenuItem;
47 import android.view.MotionEvent;
48 import android.view.SubMenu;
49 import android.view.View;
50 import android.view.ViewConfiguration;
51 import android.view.Window;
52 import android.widget.ImageButton;
53 import android.widget.ImageView;
54 import android.widget.ProgressBar;
55 import android.widget.SeekBar;
56 import android.widget.TextView;
57 import android.widget.Toast;
58 import android.widget.SeekBar.OnSeekBarChangeListener;
59
60
61 public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs,
62     View.OnTouchListener, View.OnLongClickListener
63 {
64     private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
65     
66     private boolean mOneShot = false;
67     private boolean mSeeking = false;
68     private boolean mTrackball;
69     private long mStartSeekPos = 0;
70     private long mLastSeekEventTime;
71     private IMediaPlaybackService mService = null;
72     private RepeatingImageButton mPrevButton;
73     private ImageButton mPauseButton;
74     private RepeatingImageButton mNextButton;
75     private ImageButton mRepeatButton;
76     private ImageButton mShuffleButton;
77     private ImageButton mQueueButton;
78     private Worker mAlbumArtWorker;
79     private AlbumArtHandler mAlbumArtHandler;
80     private Toast mToast;
81     private boolean mRelaunchAfterConfigChange;
82     private int mTouchSlop;
83
84     public MediaPlaybackActivity()
85     {
86     }
87
88     /** Called when the activity is first created. */
89     @Override
90     public void onCreate(Bundle icicle)
91     {
92         super.onCreate(icicle);
93         setVolumeControlStream(AudioManager.STREAM_MUSIC);
94
95         mAlbumArtWorker = new Worker("album art worker");
96         mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
97
98         requestWindowFeature(Window.FEATURE_NO_TITLE);
99         setContentView(R.layout.audio_player);
100
101         mCurrentTime = (TextView) findViewById(R.id.currenttime);
102         mTotalTime = (TextView) findViewById(R.id.totaltime);
103         mProgress = (ProgressBar) findViewById(android.R.id.progress);
104         mAlbum = (ImageView) findViewById(R.id.album);
105         mArtistName = (TextView) findViewById(R.id.artistname);
106         mAlbumName = (TextView) findViewById(R.id.albumname);
107         mTrackName = (TextView) findViewById(R.id.trackname);
108
109         View v = (View)mArtistName.getParent(); 
110         v.setOnTouchListener(this);
111         v.setOnLongClickListener(this);
112
113         v = (View)mAlbumName.getParent();
114         v.setOnTouchListener(this);
115         v.setOnLongClickListener(this);
116
117         v = (View)mTrackName.getParent();
118         v.setOnTouchListener(this);
119         v.setOnLongClickListener(this);
120         
121         mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
122         mPrevButton.setOnClickListener(mPrevListener);
123         mPrevButton.setRepeatListener(mRewListener, 260);
124         mPauseButton = (ImageButton) findViewById(R.id.pause);
125         mPauseButton.requestFocus();
126         mPauseButton.setOnClickListener(mPauseListener);
127         mNextButton = (RepeatingImageButton) findViewById(R.id.next);
128         mNextButton.setOnClickListener(mNextListener);
129         mNextButton.setRepeatListener(mFfwdListener, 260);
130         seekmethod = 1;
131
132         mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation == 
133             Resources.Configuration.NAVIGATION_TRACKBALL);*/
134         
135         mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
136         mQueueButton.setOnClickListener(mQueueListener);
137         mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
138         mShuffleButton.setOnClickListener(mShuffleListener);
139         mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
140         mRepeatButton.setOnClickListener(mRepeatListener);
141         
142         if (mProgress instanceof SeekBar) {
143             SeekBar seeker = (SeekBar) mProgress;
144             seeker.setOnSeekBarChangeListener(mSeekListener);
145         }
146         mProgress.setMax(1000);
147         
148         if (icicle != null) {
149             mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
150             mOneShot = icicle.getBoolean("oneshot");
151         } else {
152             mOneShot = getIntent().getBooleanExtra("oneshot", false);
153         }
154
155         mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
156     }
157     
158     int mInitialX = -1;
159     int mLastX = -1;
160     int mTextWidth = 0;
161     int mViewWidth = 0;
162     boolean mDraggingLabel = false;
163     
164     TextView textViewForContainer(View v) {
165         View vv = v.findViewById(R.id.artistname);
166         if (vv != null) return (TextView) vv;
167         vv = v.findViewById(R.id.albumname);
168         if (vv != null) return (TextView) vv;
169         vv = v.findViewById(R.id.trackname);
170         if (vv != null) return (TextView) vv;
171         return null;
172     }
173     
174     public boolean onTouch(View v, MotionEvent event) {
175         int action = event.getAction();
176         TextView tv = textViewForContainer(v);
177         if (tv == null) {
178             return false;
179         }
180         if (action == MotionEvent.ACTION_DOWN) {
181             v.setBackgroundColor(0xff606060);
182             mInitialX = mLastX = (int) event.getX();
183             mDraggingLabel = false;
184         } else if (action == MotionEvent.ACTION_UP ||
185                 action == MotionEvent.ACTION_CANCEL) {
186             v.setBackgroundColor(0);
187             if (mDraggingLabel) {
188                 Message msg = mLabelScroller.obtainMessage(0, tv);
189                 mLabelScroller.sendMessageDelayed(msg, 1000);
190             }
191         } else if (action == MotionEvent.ACTION_MOVE) {
192             if (mDraggingLabel) {
193                 int scrollx = tv.getScrollX();
194                 int x = (int) event.getX();
195                 int delta = mLastX - x;
196                 if (delta != 0) {
197                     mLastX = x;
198                     scrollx += delta;
199                     if (scrollx > mTextWidth) {
200                         // scrolled the text completely off the view to the left
201                         scrollx -= mTextWidth;
202                         scrollx -= mViewWidth;
203                     }
204                     if (scrollx < -mViewWidth) {
205                         // scrolled the text completely off the view to the right
206                         scrollx += mViewWidth;
207                         scrollx += mTextWidth;
208                     }
209                     tv.scrollTo(scrollx, 0);
210                 }
211                 return true;
212             }
213             int delta = mInitialX - (int) event.getX();
214             if (Math.abs(delta) > mTouchSlop) {
215                 // start moving
216                 mLabelScroller.removeMessages(0, tv);
217                 
218                 // Only turn ellipsizing off when it's not already off, because it
219                 // causes the scroll position to be reset to 0.
220                 if (tv.getEllipsize() != null) {
221                     tv.setEllipsize(null);
222                 }
223                 Layout ll = tv.getLayout();
224                 // layout might be null if the text just changed, or ellipsizing
225                 // was just turned off
226                 if (ll == null) {
227                     return false;
228                 }
229                 // get the non-ellipsized line width, to determine whether scrolling
230                 // should even be allowed
231                 mTextWidth = (int) tv.getLayout().getLineWidth(0);
232                 mViewWidth = tv.getWidth();
233                 if (mViewWidth > mTextWidth) {
234                     tv.setEllipsize(TruncateAt.END);
235                     v.cancelLongPress();
236                     return false;
237                 }
238                 mDraggingLabel = true;
239                 tv.setHorizontalFadingEdgeEnabled(true);
240                 v.cancelLongPress();
241                 return true;
242             }
243         }
244         return false; 
245     }
246
247     Handler mLabelScroller = new Handler() {
248         @Override
249         public void handleMessage(Message msg) {
250             TextView tv = (TextView) msg.obj;
251             int x = tv.getScrollX();
252             x = x * 3 / 4;
253             tv.scrollTo(x, 0);
254             if (x == 0) {
255                 tv.setEllipsize(TruncateAt.END);
256             } else {
257                 Message newmsg = obtainMessage(0, tv);
258                 mLabelScroller.sendMessageDelayed(newmsg, 15);
259             }
260         }
261     };
262     
263     public boolean onLongClick(View view) {
264
265         CharSequence title = null;
266         String mime = null;
267         String query = null;
268         String artist;
269         String album;
270         String song;
271         
272         try {
273             artist = mService.getArtistName();
274             album = mService.getAlbumName();
275             song = mService.getTrackName();
276         } catch (RemoteException ex) {
277             return true;
278         }
279         
280         boolean knownartist = !MediaFile.UNKNOWN_STRING.equals(artist);
281         boolean knownalbum = !MediaFile.UNKNOWN_STRING.equals(album);
282         
283         if (knownartist && view.equals(mArtistName.getParent())) {
284             title = artist;
285             query = artist.toString();
286             mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
287         } else if (knownalbum && view.equals(mAlbumName.getParent())) {
288             title = album;
289             if (knownartist) {
290                 query = artist + " " + album;
291             } else {
292                 query = album;
293             }
294             mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
295         } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
296             title = song;
297             if (knownartist) {
298                 query = artist + " " + song;
299             } else {
300                 query = song;
301             }
302             mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
303         } else {
304             throw new RuntimeException("shouldn't be here");
305         }
306         title = getString(R.string.mediasearch, title);
307
308         Intent i = new Intent();
309         i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
310         i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
311         i.putExtra(SearchManager.QUERY, query);
312         if(knownartist) {
313             i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
314         }
315         if(knownalbum) {
316             i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
317         }
318         i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
319         i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
320
321         startActivity(Intent.createChooser(i, title));
322         return true;
323     }
324
325     private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
326         public void onStartTrackingTouch(SeekBar bar) {
327             mLastSeekEventTime = 0;
328             mFromTouch = true;
329         }
330         public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
331             if (!fromuser || (mService == null)) return;
332             long now = SystemClock.elapsedRealtime();
333             if ((now - mLastSeekEventTime) > 250) {
334                 mLastSeekEventTime = now;
335                 mPosOverride = mDuration * progress / 1000;
336                 try {
337                     mService.seek(mPosOverride);
338                 } catch (RemoteException ex) {
339                 }
340
341                 // trackball event, allow progress updates
342                 if (!mFromTouch) {
343                     refreshNow();
344                     mPosOverride = -1;
345                 }
346             }
347         }
348         public void onStopTrackingTouch(SeekBar bar) {
349             mPosOverride = -1;
350             mFromTouch = false;
351         }
352     };
353     
354     private View.OnClickListener mQueueListener = new View.OnClickListener() {
355         public void onClick(View v) {
356             startActivity(
357                     new Intent(Intent.ACTION_EDIT)
358                     .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
359                     .putExtra("playlist", "nowplaying")
360             );
361         }
362     };
363     
364     private View.OnClickListener mShuffleListener = new View.OnClickListener() {
365         public void onClick(View v) {
366             toggleShuffle();
367         }
368     };
369
370     private View.OnClickListener mRepeatListener = new View.OnClickListener() {
371         public void onClick(View v) {
372             cycleRepeat();
373         }
374     };
375
376     private View.OnClickListener mPauseListener = new View.OnClickListener() {
377         public void onClick(View v) {
378             doPauseResume();
379         }
380     };
381
382     private View.OnClickListener mPrevListener = new View.OnClickListener() {
383         public void onClick(View v) {
384             if (mService == null) return;
385             try {
386                 if (mService.position() < 2000) {
387                     mService.prev();
388                 } else {
389                     mService.seek(0);
390                     mService.play();
391                 }
392             } catch (RemoteException ex) {
393             }
394         }
395     };
396
397     private View.OnClickListener mNextListener = new View.OnClickListener() {
398         public void onClick(View v) {
399             if (mService == null) return;
400             try {
401                 mService.next();
402             } catch (RemoteException ex) {
403             }
404         }
405     };
406
407     private RepeatingImageButton.RepeatListener mRewListener =
408         new RepeatingImageButton.RepeatListener() {
409         public void onRepeat(View v, long howlong, int repcnt) {
410             scanBackward(repcnt, howlong);
411         }
412     };
413     
414     private RepeatingImageButton.RepeatListener mFfwdListener =
415         new RepeatingImageButton.RepeatListener() {
416         public void onRepeat(View v, long howlong, int repcnt) {
417             scanForward(repcnt, howlong);
418         }
419     };
420    
421     @Override
422     public void onStop() {
423         paused = true;
424         if (mService != null && mOneShot && getChangingConfigurations() == 0) {
425             try {
426                 mService.stop();
427             } catch (RemoteException ex) {
428             }
429         }
430         mHandler.removeMessages(REFRESH);
431         unregisterReceiver(mStatusListener);
432         MusicUtils.unbindFromService(this);
433         super.onStop();
434     }
435
436     @Override
437     public void onSaveInstanceState(Bundle outState) {
438         outState.putBoolean("configchange", getChangingConfigurations() != 0);
439         outState.putBoolean("oneshot", mOneShot);
440         super.onSaveInstanceState(outState);
441     }
442     
443     @Override
444     public void onStart() {
445         super.onStart();
446         paused = false;
447
448         if (false == MusicUtils.bindToService(this, osc)) {
449             // something went wrong
450             mHandler.sendEmptyMessage(QUIT);
451         }
452         
453         IntentFilter f = new IntentFilter();
454         f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
455         f.addAction(MediaPlaybackService.META_CHANGED);
456         f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
457         registerReceiver(mStatusListener, new IntentFilter(f));
458         updateTrackInfo();
459         long next = refreshNow();
460         queueNextRefresh(next);
461     }
462     
463     @Override
464     public void onNewIntent(Intent intent) {
465         setIntent(intent);
466         mOneShot = intent.getBooleanExtra("oneshot", false);
467     }
468     
469     @Override
470     public void onResume() {
471         super.onResume();
472         updateTrackInfo();
473         setPauseButtonImage();
474     }
475     
476     @Override
477     public void onDestroy()
478     {
479         mAlbumArtWorker.quit();
480         super.onDestroy();
481         //System.out.println("***************** playback activity onDestroy\n");
482     }
483
484     @Override
485     public boolean onCreateOptionsMenu(Menu menu) {
486         super.onCreateOptionsMenu(menu);
487         // Don't show the menu items if we got launched by path/filedescriptor, since
488         // those tend to not be in the media database.
489         if (MusicUtils.getCurrentAudioId() >= 0) {
490             if (!mOneShot) {
491                 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
492                 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
493             }
494             SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
495                     R.string.add_to_playlist).setIcon(android.R.drawable.ic_menu_add);
496             MusicUtils.makePlaylistMenu(this, sub);
497             menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
498             menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
499         }
500         return true;
501     }
502
503     @Override
504     public boolean onPrepareOptionsMenu(Menu menu) {
505         MenuItem item = menu.findItem(PARTY_SHUFFLE);
506         if (item != null) {
507             int shuffle = MusicUtils.getCurrentShuffleMode();
508             if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
509                 item.setIcon(R.drawable.ic_menu_party_shuffle);
510                 item.setTitle(R.string.party_shuffle_off);
511             } else {
512                 item.setIcon(R.drawable.ic_menu_party_shuffle);
513                 item.setTitle(R.string.party_shuffle);
514             }
515         }
516         return true;
517     }
518
519     @Override
520     public boolean onOptionsItemSelected(MenuItem item) {
521         Intent intent;
522         try {
523             switch (item.getItemId()) {
524                 case GOTO_START:
525                     intent = new Intent();
526                     intent.setClass(this, MusicBrowserActivity.class);
527                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
528                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
529                     startActivity(intent);
530                     break;
531                 case USE_AS_RINGTONE: {
532                     // Set the system setting to make this the current ringtone
533                     if (mService != null) {
534                         MusicUtils.setRingtone(this, mService.getAudioId());
535                     }
536                     return true;
537                 }
538                 case PARTY_SHUFFLE:
539                     if (mService != null) {
540                         int shuffle = mService.getShuffleMode();
541                         if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
542                             mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
543                         } else {
544                             mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
545                         }
546                     }
547                     setShuffleButtonImage();
548                     break;
549                     
550                 case NEW_PLAYLIST: {
551                     intent = new Intent();
552                     intent.setClass(this, CreatePlaylist.class);
553                     startActivityForResult(intent, NEW_PLAYLIST);
554                     return true;
555                 }
556
557                 case PLAYLIST_SELECTED: {
558                     int [] list = new int[1];
559                     list[0] = MusicUtils.getCurrentAudioId();
560                     int playlist = item.getIntent().getIntExtra("playlist", 0);
561                     MusicUtils.addToPlaylist(this, list, playlist);
562                     return true;
563                 }
564                 
565                 case DELETE_ITEM: {
566                     if (mService != null) {
567                         int [] list = new int[1];
568                         list[0] = MusicUtils.getCurrentAudioId();
569                         Bundle b = new Bundle();
570                         b.putString("description", getString(R.string.delete_song_desc,
571                                 mService.getTrackName()));
572                         b.putIntArray("items", list);
573                         intent = new Intent();
574                         intent.setClass(this, DeleteItems.class);
575                         intent.putExtras(b);
576                         startActivityForResult(intent, -1);
577                     }
578                     return true;
579                 }
580             }
581         } catch (RemoteException ex) {
582         }
583         return super.onOptionsItemSelected(item);
584     }
585     
586     @Override
587     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
588         if (resultCode != RESULT_OK) {
589             return;
590         }
591         switch (requestCode) {
592             case NEW_PLAYLIST:
593                 Uri uri = intent.getData();
594                 if (uri != null) {
595                     int [] list = new int[1];
596                     list[0] = MusicUtils.getCurrentAudioId();
597                     int playlist = Integer.parseInt(uri.getLastPathSegment());
598                     MusicUtils.addToPlaylist(this, list, playlist);
599                 }
600                 break;
601         }
602     }
603     private final int keyboard[][] = {
604         {
605             KeyEvent.KEYCODE_Q,
606             KeyEvent.KEYCODE_W,
607             KeyEvent.KEYCODE_E,
608             KeyEvent.KEYCODE_R,
609             KeyEvent.KEYCODE_T,
610             KeyEvent.KEYCODE_Y,
611             KeyEvent.KEYCODE_U,
612             KeyEvent.KEYCODE_I,
613             KeyEvent.KEYCODE_O,
614             KeyEvent.KEYCODE_P,
615         },
616         {
617             KeyEvent.KEYCODE_A,
618             KeyEvent.KEYCODE_S,
619             KeyEvent.KEYCODE_D,
620             KeyEvent.KEYCODE_F,
621             KeyEvent.KEYCODE_G,
622             KeyEvent.KEYCODE_H,
623             KeyEvent.KEYCODE_J,
624             KeyEvent.KEYCODE_K,
625             KeyEvent.KEYCODE_L,
626             KeyEvent.KEYCODE_DEL,
627         },
628         {
629             KeyEvent.KEYCODE_Z,
630             KeyEvent.KEYCODE_X,
631             KeyEvent.KEYCODE_C,
632             KeyEvent.KEYCODE_V,
633             KeyEvent.KEYCODE_B,
634             KeyEvent.KEYCODE_N,
635             KeyEvent.KEYCODE_M,
636             KeyEvent.KEYCODE_COMMA,
637             KeyEvent.KEYCODE_PERIOD,
638             KeyEvent.KEYCODE_ENTER
639         }
640
641     };
642
643     private int lastX;
644     private int lastY;
645
646     private boolean seekMethod1(int keyCode)
647     {
648         for(int x=0;x<10;x++) {
649             for(int y=0;y<3;y++) {
650                 if(keyboard[y][x] == keyCode) {
651                     int dir = 0;
652                     // top row
653                     if(x == lastX && y == lastY) dir = 0;
654                     else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
655                     else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
656                     // bottom row
657                     else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
658                     else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
659                     // moving up
660                     else if (y < lastY && x <= 4) dir = 1; 
661                     else if (y < lastY && x >= 5) dir = -1; 
662                     // moving down
663                     else if (y > lastY && x <= 4) dir = -1; 
664                     else if (y > lastY && x >= 5) dir = 1; 
665                     lastX = x;
666                     lastY = y;
667                     try {
668                         mService.seek(mService.position() + dir * 5);
669                     } catch (RemoteException ex) {
670                     }
671                     refreshNow();
672                     return true;
673                 }
674             }
675         }
676         lastX = -1;
677         lastY = -1;
678         return false;
679     }
680
681     private boolean seekMethod2(int keyCode)
682     {
683         if (mService == null) return false;
684         for(int i=0;i<10;i++) {
685             if(keyboard[0][i] == keyCode) {
686                 int seekpercentage = 100*i/10;
687                 try {
688                     mService.seek(mService.duration() * seekpercentage / 100);
689                 } catch (RemoteException ex) {
690                 }
691                 refreshNow();
692                 return true;
693             }
694         }
695         return false;
696     }
697
698     @Override
699     public boolean onKeyUp(int keyCode, KeyEvent event) {
700         try {
701             switch(keyCode)
702             {
703                 case KeyEvent.KEYCODE_DPAD_LEFT:
704                     if (mTrackball) {
705                         break;
706                     }
707                     if (mService != null) {
708                         if (!mSeeking && mStartSeekPos >= 0) {
709                             mPauseButton.requestFocus();
710                             if (mStartSeekPos < 1000) {
711                                 mService.prev();
712                             } else {
713                                 mService.seek(0);
714                             }
715                         } else {
716                             scanBackward(-1, event.getEventTime() - event.getDownTime());
717                             mPauseButton.requestFocus();
718                             mStartSeekPos = -1;
719                         }
720                     }
721                     mSeeking = false;
722                     mPosOverride = -1;
723                     return true;
724                 case KeyEvent.KEYCODE_DPAD_RIGHT:
725                     if (mTrackball) {
726                         break;
727                     }
728                     if (mService != null) {
729                         if (!mSeeking && mStartSeekPos >= 0) {
730                             mPauseButton.requestFocus();
731                             mService.next();
732                         } else {
733                             scanForward(-1, event.getEventTime() - event.getDownTime());
734                             mPauseButton.requestFocus();
735                             mStartSeekPos = -1;
736                         }
737                     }
738                     mSeeking = false;
739                     mPosOverride = -1;
740                     return true;
741             }
742         } catch (RemoteException ex) {
743         }
744         return super.onKeyUp(keyCode, event);
745     }
746
747     @Override
748     public boolean onKeyDown(int keyCode, KeyEvent event)
749     {
750         int direction = -1;
751         int repcnt = event.getRepeatCount();
752
753         if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
754             return true;
755
756         switch(keyCode)
757         {
758 /*
759             // image scale
760             case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
761             case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
762             // image translate
763             case KeyEvent.KEYCODE_W: av.adjustParams(    0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
764             case KeyEvent.KEYCODE_X: av.adjustParams(    0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
765             case KeyEvent.KEYCODE_A: av.adjustParams(    0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
766             case KeyEvent.KEYCODE_D: av.adjustParams(    0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
767             // camera rotation
768             case KeyEvent.KEYCODE_R: av.adjustParams(    0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
769             case KeyEvent.KEYCODE_U: av.adjustParams(    0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
770             // camera translate
771             case KeyEvent.KEYCODE_Y: av.adjustParams(    0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
772             case KeyEvent.KEYCODE_N: av.adjustParams(    0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
773             case KeyEvent.KEYCODE_G: av.adjustParams(    0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
774             case KeyEvent.KEYCODE_J: av.adjustParams(    0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
775
776 */
777
778             case KeyEvent.KEYCODE_SLASH:
779                 seekmethod = 1 - seekmethod;
780                 return true;
781
782             case KeyEvent.KEYCODE_DPAD_LEFT:
783                 if (mTrackball) {
784                     break;
785                 }
786                 if (!mPrevButton.hasFocus()) {
787                     mPrevButton.requestFocus();
788                 }
789                 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
790                 return true;
791             case KeyEvent.KEYCODE_DPAD_RIGHT:
792                 if (mTrackball) {
793                     break;
794                 }
795                 if (!mNextButton.hasFocus()) {
796                     mNextButton.requestFocus();
797                 }
798                 scanForward(repcnt, event.getEventTime() - event.getDownTime());
799                 return true;
800
801             case KeyEvent.KEYCODE_S:
802                 toggleShuffle();
803                 return true;
804
805             case KeyEvent.KEYCODE_DPAD_CENTER:
806             case KeyEvent.KEYCODE_SPACE:
807                 doPauseResume();
808                 return true;
809         }
810         return super.onKeyDown(keyCode, event);
811     }
812     
813     private void scanBackward(int repcnt, long delta) {
814         if(mService == null) return;
815         try {
816             if(repcnt == 0) {
817                 mStartSeekPos = mService.position();
818                 mLastSeekEventTime = 0;
819                 mSeeking = false;
820             } else {
821                 mSeeking = true;
822                 if (delta < 5000) {
823                     // seek at 10x speed for the first 5 seconds
824                     delta = delta * 10; 
825                 } else {
826                     // seek at 40x after that
827                     delta = 50000 + (delta - 5000) * 40;
828                 }
829                 long newpos = mStartSeekPos - delta;
830                 if (newpos < 0) {
831                     // move to previous track
832                     mService.prev();
833                     long duration = mService.duration();
834                     mStartSeekPos += duration;
835                     newpos += duration;
836                 }
837                 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
838                     mService.seek(newpos);
839                     mLastSeekEventTime = delta;
840                 }
841                 if (repcnt >= 0) {
842                     mPosOverride = newpos;
843                 } else {
844                     mPosOverride = -1;
845                 }
846                 refreshNow();
847             }
848         } catch (RemoteException ex) {
849         }
850     }
851
852     private void scanForward(int repcnt, long delta) {
853         if(mService == null) return;
854         try {
855             if(repcnt == 0) {
856                 mStartSeekPos = mService.position();
857                 mLastSeekEventTime = 0;
858                 mSeeking = false;
859             } else {
860                 mSeeking = true;
861                 if (delta < 5000) {
862                     // seek at 10x speed for the first 5 seconds
863                     delta = delta * 10; 
864                 } else {
865                     // seek at 40x after that
866                     delta = 50000 + (delta - 5000) * 40;
867                 }
868                 long newpos = mStartSeekPos + delta;
869                 long duration = mService.duration();
870                 if (newpos >= duration) {
871                     // move to next track
872                     mService.next();
873                     mStartSeekPos -= duration; // is OK to go negative
874                     newpos -= duration;
875                 }
876                 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
877                     mService.seek(newpos);
878                     mLastSeekEventTime = delta;
879                 }
880                 if (repcnt >= 0) {
881                     mPosOverride = newpos;
882                 } else {
883                     mPosOverride = -1;
884                 }
885                 refreshNow();
886             }
887         } catch (RemoteException ex) {
888         }
889     }
890     
891     private void doPauseResume() {
892         try {
893             if(mService != null) {
894                 if (mService.isPlaying()) {
895                     mService.pause();
896                 } else {
897                     mService.play();
898                 }
899                 refreshNow();
900                 setPauseButtonImage();
901             }
902         } catch (RemoteException ex) {
903         }
904     }
905     
906     private void toggleShuffle() {
907         if (mService == null) {
908             return;
909         }
910         try {
911             int shuffle = mService.getShuffleMode();
912             if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
913                 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
914                 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
915                     mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
916                     setRepeatButtonImage();
917                 }
918                 showToast(R.string.shuffle_on_notif);
919             } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
920                     shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
921                 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
922                 showToast(R.string.shuffle_off_notif);
923             } else {
924                 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
925             }
926             setShuffleButtonImage();
927         } catch (RemoteException ex) {
928         }
929     }
930     
931     private void cycleRepeat() {
932         if (mService == null) {
933             return;
934         }
935         try {
936             int mode = mService.getRepeatMode();
937             if (mode == MediaPlaybackService.REPEAT_NONE) {
938                 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
939                 showToast(R.string.repeat_all_notif);
940             } else if (mode == MediaPlaybackService.REPEAT_ALL) {
941                 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
942                 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
943                     mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
944                     setShuffleButtonImage();
945                 }
946                 showToast(R.string.repeat_current_notif);
947             } else {
948                 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
949                 showToast(R.string.repeat_off_notif);
950             }
951             setRepeatButtonImage();
952         } catch (RemoteException ex) {
953         }
954         
955     }
956     
957     private void showToast(int resid) {
958         if (mToast == null) {
959             mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
960         }
961         mToast.setText(resid);
962         mToast.show();
963     }
964
965     private void startPlayback() {
966
967         if(mService == null)
968             return;
969         Intent intent = getIntent();
970         String filename = "";
971         Uri uri = intent.getData();
972         if (uri != null && uri.toString().length() > 0) {
973             // If this is a file:// URI, just use the path directly instead
974             // of going through the open-from-filedescriptor codepath.
975             String scheme = uri.getScheme();
976             if ("file".equals(scheme)) {
977                 filename = uri.getPath();
978             } else {
979                 filename = uri.toString();
980             }
981             try {
982                 mOneShot = true;
983                 if (! mRelaunchAfterConfigChange) {
984                     mService.stop();
985                     mService.openfile(filename);
986                     mService.play();
987                 }
988             } catch (Exception ex) {
989                 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
990             }
991         }
992
993         updateTrackInfo();
994         long next = refreshNow();
995         queueNextRefresh(next);
996     }
997
998     private ServiceConnection osc = new ServiceConnection() {
999             public void onServiceConnected(ComponentName classname, IBinder obj) {
1000                 mService = IMediaPlaybackService.Stub.asInterface(obj);
1001                 if (MusicUtils.sService == null) {
1002                     MusicUtils.sService = mService;
1003                 }
1004                 startPlayback();
1005                 try {
1006                     // Assume something is playing when the service says it is,
1007                     // but also if the audio ID is valid but the service is paused.
1008                     if (mService.getAudioId() >= 0 || mService.isPlaying() ||
1009                             mService.getPath() != null) {
1010                         // something is playing now, we're done
1011                         if (mOneShot || mService.getAudioId() < 0) {
1012                             mRepeatButton.setVisibility(View.INVISIBLE);
1013                             mShuffleButton.setVisibility(View.INVISIBLE);
1014                             mQueueButton.setVisibility(View.INVISIBLE);
1015                         } else {
1016                             mRepeatButton.setVisibility(View.VISIBLE);
1017                             mShuffleButton.setVisibility(View.VISIBLE);
1018                             mQueueButton.setVisibility(View.VISIBLE);
1019                             setRepeatButtonImage();
1020                             setShuffleButtonImage();
1021                         }
1022                         setPauseButtonImage();
1023                         return;
1024                     }
1025                 } catch (RemoteException ex) {
1026                 }
1027                 // Service is dead or not playing anything. If we got here as part
1028                 // of a "play this file" Intent, exit. Otherwise go to the Music
1029                 // app start screen.
1030                 if (getIntent().getData() == null) {
1031                     Intent intent = new Intent(Intent.ACTION_MAIN);
1032                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1033                     intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
1034                     startActivity(intent);
1035                 }
1036                 finish();
1037             }
1038             public void onServiceDisconnected(ComponentName classname) {
1039             }
1040     };
1041
1042     private void setRepeatButtonImage() {
1043         try {
1044             switch (mService.getRepeatMode()) {
1045                 case MediaPlaybackService.REPEAT_ALL:
1046                     mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
1047                     break;
1048                 case MediaPlaybackService.REPEAT_CURRENT:
1049                     mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
1050                     break;
1051                 default:
1052                     mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
1053                     break;
1054             }
1055         } catch (RemoteException ex) {
1056         }
1057     }
1058     
1059     private void setShuffleButtonImage() {
1060         try {
1061             switch (mService.getShuffleMode()) {
1062                 case MediaPlaybackService.SHUFFLE_NONE:
1063                     mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
1064                     break;
1065                 case MediaPlaybackService.SHUFFLE_AUTO:
1066                     mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
1067                     break;
1068                 default:
1069                     mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
1070                     break;
1071             }
1072         } catch (RemoteException ex) {
1073         }
1074     }
1075     
1076     private void setPauseButtonImage() {
1077         try {
1078             if (mService != null && mService.isPlaying()) {
1079                 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
1080             } else {
1081                 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
1082             }
1083         } catch (RemoteException ex) {
1084         }
1085     }
1086     
1087     private ImageView mAlbum;
1088     private TextView mCurrentTime;
1089     private TextView mTotalTime;
1090     private TextView mArtistName;
1091     private TextView mAlbumName;
1092     private TextView mTrackName;
1093     private ProgressBar mProgress;
1094     private long mPosOverride = -1;
1095     private boolean mFromTouch = false;
1096     private long mDuration;
1097     private int seekmethod;
1098     private boolean paused;
1099
1100     private static final int REFRESH = 1;
1101     private static final int QUIT = 2;
1102     private static final int GET_ALBUM_ART = 3;
1103     private static final int ALBUM_ART_DECODED = 4;
1104
1105     private void queueNextRefresh(long delay) {
1106         if (!paused) {
1107             Message msg = mHandler.obtainMessage(REFRESH);
1108             mHandler.removeMessages(REFRESH);
1109             mHandler.sendMessageDelayed(msg, delay);
1110         }
1111     }
1112
1113     private long refreshNow() {
1114         if(mService == null)
1115             return 500;
1116         try {
1117             long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
1118             long remaining = 1000 - (pos % 1000);
1119             if ((pos >= 0) && (mDuration > 0)) {
1120                 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
1121                 
1122                 if (mService.isPlaying()) {
1123                     mCurrentTime.setVisibility(View.VISIBLE);
1124                 } else {
1125                     // blink the counter
1126                     int vis = mCurrentTime.getVisibility();
1127                     mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1128                     remaining = 500;
1129                 }
1130
1131                 mProgress.setProgress((int) (1000 * pos / mDuration));
1132             } else {
1133                 mCurrentTime.setText("--:--");
1134                 mProgress.setProgress(1000);
1135             }
1136             // return the number of milliseconds until the next full second, so
1137             // the counter can be updated at just the right time
1138             return remaining;
1139         } catch (RemoteException ex) {
1140         }
1141         return 500;
1142     }
1143     
1144     private final Handler mHandler = new Handler() {
1145         @Override
1146         public void handleMessage(Message msg) {
1147             switch (msg.what) {
1148                 case ALBUM_ART_DECODED:
1149                     mAlbum.setImageBitmap((Bitmap)msg.obj);
1150                     mAlbum.getDrawable().setDither(true);
1151                     break;
1152
1153                 case REFRESH:
1154                     long next = refreshNow();
1155                     queueNextRefresh(next);
1156                     break;
1157                     
1158                 case QUIT:
1159                     // This can be moved back to onCreate once the bug that prevents
1160                     // Dialogs from being started from onCreate/onResume is fixed.
1161                     new AlertDialog.Builder(MediaPlaybackActivity.this)
1162                             .setTitle(R.string.service_start_error_title)
1163                             .setMessage(R.string.service_start_error_msg)
1164                             .setPositiveButton(R.string.service_start_error_button,
1165                                     new DialogInterface.OnClickListener() {
1166                                         public void onClick(DialogInterface dialog, int whichButton) {
1167                                             finish();
1168                                         }
1169                                     })
1170                             .setCancelable(false)
1171                             .show();
1172                     break;
1173
1174                 default:
1175                     break;
1176             }
1177         }
1178     };
1179
1180     private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1181         @Override
1182         public void onReceive(Context context, Intent intent) {
1183             String action = intent.getAction();
1184             if (action.equals(MediaPlaybackService.META_CHANGED)) {
1185                 // redraw the artist/title info and
1186                 // set new max for progress bar
1187                 updateTrackInfo();
1188                 setPauseButtonImage();
1189                 queueNextRefresh(1);
1190             } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1191                 if (mOneShot) {
1192                     finish();
1193                 } else {
1194                     setPauseButtonImage();
1195                 }
1196             } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1197                 setPauseButtonImage();
1198             }
1199         }
1200     };
1201
1202     private void updateTrackInfo() {
1203         if (mService == null) {
1204             return;
1205         }
1206         try {
1207             String path = mService.getPath();
1208             if (path == null) {
1209                 finish();
1210                 return;
1211             }
1212             
1213             if (mService.getAudioId() < 0 && path.toLowerCase().startsWith("http://")) {
1214                 ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
1215                 ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
1216                 mAlbum.setVisibility(View.GONE);
1217                 mTrackName.setText(path);
1218                 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1219                 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, -1, 0).sendToTarget();
1220             } else {
1221                 ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
1222                 ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
1223                 String artistName = mService.getArtistName();
1224                 if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1225                     artistName = getString(R.string.unknown_artist_name);
1226                 }
1227                 mArtistName.setText(artistName);
1228                 String albumName = mService.getAlbumName();
1229                 int albumid = mService.getAlbumId();
1230                 if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
1231                     albumName = getString(R.string.unknown_album_name);
1232                     albumid = -1;
1233                 }
1234                 mAlbumName.setText(albumName);
1235                 mTrackName.setText(mService.getTrackName());
1236                 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1237                 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, albumid, 0).sendToTarget();
1238                 mAlbum.setVisibility(View.VISIBLE);
1239             }
1240             mDuration = mService.duration();
1241             mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1242         } catch (RemoteException ex) {
1243             finish();
1244         }
1245     }
1246     
1247     public class AlbumArtHandler extends Handler {
1248         private int mAlbumId = -1;
1249         
1250         public AlbumArtHandler(Looper looper) {
1251             super(looper);
1252         }
1253         public void handleMessage(Message msg)
1254         {
1255             int albumid = msg.arg1;
1256             if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1257                 // while decoding the new image, show the default album art
1258                 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1259                 mHandler.removeMessages(ALBUM_ART_DECODED);
1260                 mHandler.sendMessageDelayed(numsg, 300);
1261                 Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, albumid);
1262                 if (bm == null) {
1263                     bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
1264                     albumid = -1;
1265                 }
1266                 if (bm != null) {
1267                     numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1268                     mHandler.removeMessages(ALBUM_ART_DECODED);
1269                     mHandler.sendMessage(numsg);
1270                 }
1271                 mAlbumId = albumid;
1272             }
1273         }
1274     }
1275     
1276     private class Worker implements Runnable {
1277         private final Object mLock = new Object();
1278         private Looper mLooper;
1279         
1280         /**
1281          * Creates a worker thread with the given name. The thread
1282          * then runs a {@link android.os.Looper}.
1283          * @param name A name for the new thread
1284          */
1285         Worker(String name) {
1286             Thread t = new Thread(null, this, name);
1287             t.setPriority(Thread.MIN_PRIORITY);
1288             t.start();
1289             synchronized (mLock) {
1290                 while (mLooper == null) {
1291                     try {
1292                         mLock.wait();
1293                     } catch (InterruptedException ex) {
1294                     }
1295                 }
1296             }
1297         }
1298         
1299         public Looper getLooper() {
1300             return mLooper;
1301         }
1302         
1303         public void run() {
1304             synchronized (mLock) {
1305                 Looper.prepare();
1306                 mLooper = Looper.myLooper();
1307                 mLock.notifyAll();
1308             }
1309             Looper.loop();
1310         }
1311         
1312         public void quit() {
1313             mLooper.quit();
1314         }
1315     }
1316 }
1317