OSDN Git Service

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