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