OSDN Git Service

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