OSDN Git Service

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