OSDN Git Service

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