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