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