OSDN Git Service

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