OSDN Git Service

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