OSDN Git Service

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