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