OSDN Git Service

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