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