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