OSDN Git Service

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