OSDN Git Service

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