1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.keyguard; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.content.res.Configuration; 22 import android.graphics.Bitmap; 23 import android.graphics.ColorMatrix; 24 import android.graphics.ColorMatrixColorFilter; 25 import android.graphics.PorterDuff; 26 import android.graphics.PorterDuffXfermode; 27 import android.graphics.drawable.Drawable; 28 import android.media.AudioManager; 29 import android.media.MediaMetadataEditor; 30 import android.media.MediaMetadataRetriever; 31 import android.media.RemoteControlClient; 32 import android.media.RemoteController; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.os.SystemClock; 36 import android.text.TextUtils; 37 import android.text.format.DateFormat; 38 import android.transition.ChangeBounds; 39 import android.transition.ChangeText; 40 import android.transition.Fade; 41 import android.transition.TransitionManager; 42 import android.transition.TransitionSet; 43 import android.util.AttributeSet; 44 import android.util.DisplayMetrics; 45 import android.util.Log; 46 import android.view.KeyEvent; 47 import android.view.View; 48 import android.view.ViewGroup; 49 import android.widget.FrameLayout; 50 import android.widget.ImageView; 51 import android.widget.SeekBar; 52 import android.widget.TextView; 53 54 import java.text.SimpleDateFormat; 55 import java.util.Date; 56 import java.util.TimeZone; 57 58 /** 59 * This is the widget responsible for showing music controls in keyguard. 60 */ 61 public class KeyguardTransportControlView extends FrameLayout { 62 63 private static final int RESET_TO_METADATA_DELAY = 5000; 64 protected static final boolean DEBUG = false; 65 protected static final String TAG = "TransportControlView"; 66 67 private static final boolean ANIMATE_TRANSITIONS = true; 68 protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000; 69 70 private ViewGroup mMetadataContainer; 71 private ViewGroup mInfoContainer; 72 private TextView mTrackTitle; 73 private TextView mTrackArtistAlbum; 74 75 private View mTransientSeek; 76 private SeekBar mTransientSeekBar; 77 private TextView mTransientSeekTimeElapsed; 78 private TextView mTransientSeekTimeTotal; 79 80 private ImageView mBtnPrev; 81 private ImageView mBtnPlay; 82 private ImageView mBtnNext; 83 private Metadata mMetadata = new Metadata(); 84 private int mTransportControlFlags; 85 private int mCurrentPlayState; 86 private AudioManager mAudioManager; 87 private RemoteController mRemoteController; 88 89 private ImageView mBadge; 90 91 private boolean mSeekEnabled; 92 private java.text.DateFormat mFormat; 93 94 private Date mTempDate = new Date(); 95 96 /** 97 * The metadata which should be populated into the view once we've been attached 98 */ 99 private RemoteController.MetadataEditor mPopulateMetadataWhenAttached = null; 100 101 private RemoteController.OnClientUpdateListener mRCClientUpdateListener = 102 new RemoteController.OnClientUpdateListener() { 103 @Override 104 public void onClientChange(boolean clearing) { 105 if (clearing) { 106 clearMetadata(); 107 } 108 } 109 110 @Override 111 public void onClientPlaybackStateUpdate(int state) { 112 updatePlayPauseState(state); 113 } 114 115 @Override 116 public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, 117 long currentPosMs, float speed) { 118 updatePlayPauseState(state); 119 if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state + 120 ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs + 121 ", speed=" + speed + ")"); 122 123 removeCallbacks(mUpdateSeekBars); 124 // Since the music client may be responding to historical events that cause the 125 // playback state to change dramatically, wait until things become quiescent before 126 // resuming automatic scrub position update. 127 if (mTransientSeek.getVisibility() == View.VISIBLE 128 && playbackPositionShouldMove(mCurrentPlayState)) { 129 postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR); 130 } 131 } 132 133 @Override 134 public void onClientTransportControlUpdate(int transportControlFlags) { 135 updateTransportControls(transportControlFlags); 136 } 137 138 @Override 139 public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) { 140 updateMetadata(metadataEditor); 141 } 142 }; 143 144 private class UpdateSeekBarRunnable implements Runnable { 145 public void run() { 146 boolean seekAble = updateOnce(); 147 if (seekAble) { 148 removeCallbacks(this); 149 postDelayed(this, 1000); 150 } 151 } 152 public boolean updateOnce() { 153 return updateSeekBars(); 154 } 155 }; 156 157 private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable(); 158 159 private final Runnable mResetToMetadata = new Runnable() { 160 public void run() { 161 resetToMetadata(); 162 } 163 }; 164 165 private final OnClickListener mTransportCommandListener = new OnClickListener() { 166 public void onClick(View v) { 167 int keyCode = -1; 168 if (v == mBtnPrev) { 169 keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS; 170 } else if (v == mBtnNext) { 171 keyCode = KeyEvent.KEYCODE_MEDIA_NEXT; 172 } else if (v == mBtnPlay) { 173 keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; 174 } 175 if (keyCode != -1) { 176 sendMediaButtonClick(keyCode); 177 delayResetToMetadata(); // if the scrub bar is showing, keep showing it. 178 } 179 } 180 }; 181 182 private final OnLongClickListener mTransportShowSeekBarListener = new OnLongClickListener() { 183 @Override 184 public boolean onLongClick(View v) { 185 if (mSeekEnabled) { 186 return tryToggleSeekBar(); 187 } 188 return false; 189 } 190 }; 191 192 // This class is here to throttle scrub position updates to the music client 193 class FutureSeekRunnable implements Runnable { 194 private int mProgress; 195 private boolean mPending; 196 197 public void run() { 198 scrubTo(mProgress); 199 mPending = false; 200 } 201 202 void setProgress(int progress) { 203 mProgress = progress; 204 if (!mPending) { 205 mPending = true; 206 postDelayed(this, 30); 207 } 208 } 209 }; 210 211 // This is here because RemoteControlClient's method isn't visible :/ 212 private final static boolean playbackPositionShouldMove(int playstate) { 213 switch(playstate) { 214 case RemoteControlClient.PLAYSTATE_STOPPED: 215 case RemoteControlClient.PLAYSTATE_PAUSED: 216 case RemoteControlClient.PLAYSTATE_BUFFERING: 217 case RemoteControlClient.PLAYSTATE_ERROR: 218 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: 219 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: 220 return false; 221 case RemoteControlClient.PLAYSTATE_PLAYING: 222 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: 223 case RemoteControlClient.PLAYSTATE_REWINDING: 224 default: 225 return true; 226 } 227 } 228 229 private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable(); 230 231 private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = 232 new SeekBar.OnSeekBarChangeListener() { 233 @Override 234 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 235 if (fromUser) { 236 mFutureSeekRunnable.setProgress(progress); 237 delayResetToMetadata(); 238 mTempDate.setTime(progress); 239 mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); 240 } else { 241 updateSeekDisplay(); 242 } 243 } 244 245 @Override 246 public void onStartTrackingTouch(SeekBar seekBar) { 247 delayResetToMetadata(); 248 removeCallbacks(mUpdateSeekBars); // don't update during user interaction 249 } 250 251 @Override 252 public void onStopTrackingTouch(SeekBar seekBar) { 253 } 254 }; 255 256 private static final int TRANSITION_DURATION = 200; 257 private final TransitionSet mMetadataChangeTransition; 258 259 KeyguardHostView.TransportControlCallback mTransportControlCallback; 260 261 private final KeyguardUpdateMonitorCallback mUpdateMonitor 262 = new KeyguardUpdateMonitorCallback() { 263 public void onScreenTurnedOff(int why) { 264 setEnableMarquee(false); 265 } 266 public void onScreenTurnedOn() { 267 setEnableMarquee(true); 268 } 269 }; 270 271 public KeyguardTransportControlView(Context context, AttributeSet attrs) { 272 super(context, attrs); 273 if (DEBUG) Log.v(TAG, "Create TCV " + this); 274 mAudioManager = new AudioManager(mContext); 275 mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback 276 mRemoteController = new RemoteController(context, mRCClientUpdateListener); 277 278 final DisplayMetrics dm = context.getResources().getDisplayMetrics(); 279 final int dim = Math.max(dm.widthPixels, dm.heightPixels); 280 mRemoteController.setArtworkConfiguration(true, dim, dim); 281 282 final ChangeText tc = new ChangeText(); 283 tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN); 284 final TransitionSet inner = new TransitionSet(); 285 inner.addTransition(tc).addTransition(new ChangeBounds()); 286 final TransitionSet tg = new TransitionSet(); 287 tg.addTransition(new Fade(Fade.OUT)).addTransition(inner). 288 addTransition(new Fade(Fade.IN)); 289 tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL); 290 tg.setDuration(TRANSITION_DURATION); 291 mMetadataChangeTransition = tg; 292 } 293 294 private void updateTransportControls(int transportControlFlags) { 295 mTransportControlFlags = transportControlFlags; 296 setSeekBarsEnabled( 297 (transportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE) != 0); 298 } 299 300 void setSeekBarsEnabled(boolean enabled) { 301 if (enabled == mSeekEnabled) return; 302 303 mSeekEnabled = enabled; 304 if (mTransientSeek.getVisibility() == VISIBLE && !enabled) { 305 mTransientSeek.setVisibility(INVISIBLE); 306 mMetadataContainer.setVisibility(VISIBLE); 307 cancelResetToMetadata(); 308 } 309 } 310 311 public void setTransportControlCallback(KeyguardHostView.TransportControlCallback 312 transportControlCallback) { 313 mTransportControlCallback = transportControlCallback; 314 } 315 316 private void setEnableMarquee(boolean enabled) { 317 if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee"); 318 if (mTrackTitle != null) mTrackTitle.setSelected(enabled); 319 if (mTrackArtistAlbum != null) mTrackTitle.setSelected(enabled); 320 } 321 322 @Override 323 public void onFinishInflate() { 324 super.onFinishInflate(); 325 mInfoContainer = (ViewGroup) findViewById(R.id.info_container); 326 mMetadataContainer = (ViewGroup) findViewById(R.id.metadata_container); 327 mBadge = (ImageView) findViewById(R.id.badge); 328 mTrackTitle = (TextView) findViewById(R.id.title); 329 mTrackArtistAlbum = (TextView) findViewById(R.id.artist_album); 330 mTransientSeek = findViewById(R.id.transient_seek); 331 mTransientSeekBar = (SeekBar) findViewById(R.id.transient_seek_bar); 332 mTransientSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); 333 mTransientSeekTimeElapsed = (TextView) findViewById(R.id.transient_seek_time_elapsed); 334 mTransientSeekTimeTotal = (TextView) findViewById(R.id.transient_seek_time_remaining); 335 mBtnPrev = (ImageView) findViewById(R.id.btn_prev); 336 mBtnPlay = (ImageView) findViewById(R.id.btn_play); 337 mBtnNext = (ImageView) findViewById(R.id.btn_next); 338 final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext }; 339 for (View view : buttons) { 340 view.setOnClickListener(mTransportCommandListener); 341 view.setOnLongClickListener(mTransportShowSeekBarListener); 342 } 343 final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); 344 setEnableMarquee(screenOn); 345 // Allow long-press anywhere else in this view to show the seek bar 346 setOnLongClickListener(mTransportShowSeekBarListener); 347 } 348 349 @Override 350 public void onAttachedToWindow() { 351 super.onAttachedToWindow(); 352 if (DEBUG) Log.v(TAG, "onAttachToWindow()"); 353 if (mPopulateMetadataWhenAttached != null) { 354 updateMetadata(mPopulateMetadataWhenAttached); 355 mPopulateMetadataWhenAttached = null; 356 } 357 if (DEBUG) Log.v(TAG, "Registering TCV " + this); 358 mMetadata.clear(); 359 mAudioManager.registerRemoteController(mRemoteController); 360 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitor); 361 } 362 363 @Override 364 protected void onConfigurationChanged(Configuration newConfig) { 365 super.onConfigurationChanged(newConfig); 366 final DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); 367 final int dim = Math.max(dm.widthPixels, dm.heightPixels); 368 mRemoteController.setArtworkConfiguration(true, dim, dim); 369 } 370 371 @Override 372 public void onDetachedFromWindow() { 373 if (DEBUG) Log.v(TAG, "onDetachFromWindow()"); 374 super.onDetachedFromWindow(); 375 if (DEBUG) Log.v(TAG, "Unregistering TCV " + this); 376 mAudioManager.unregisterRemoteController(mRemoteController); 377 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor); 378 mMetadata.clear(); 379 removeCallbacks(mUpdateSeekBars); 380 } 381 382 @Override 383 protected Parcelable onSaveInstanceState() { 384 SavedState ss = new SavedState(super.onSaveInstanceState()); 385 ss.artist = mMetadata.artist; 386 ss.trackTitle = mMetadata.trackTitle; 387 ss.albumTitle = mMetadata.albumTitle; 388 ss.duration = mMetadata.duration; 389 ss.bitmap = mMetadata.bitmap; 390 return ss; 391 } 392 393 @Override 394 protected void onRestoreInstanceState(Parcelable state) { 395 if (!(state instanceof SavedState)) { 396 super.onRestoreInstanceState(state); 397 return; 398 } 399 SavedState ss = (SavedState) state; 400 super.onRestoreInstanceState(ss.getSuperState()); 401 mMetadata.artist = ss.artist; 402 mMetadata.trackTitle = ss.trackTitle; 403 mMetadata.albumTitle = ss.albumTitle; 404 mMetadata.duration = ss.duration; 405 mMetadata.bitmap = ss.bitmap; 406 populateMetadata(); 407 } 408 409 void setBadgeIcon(Drawable bmp) { 410 mBadge.setImageDrawable(bmp); 411 412 final ColorMatrix cm = new ColorMatrix(); 413 cm.setSaturation(0); 414 mBadge.setColorFilter(new ColorMatrixColorFilter(cm)); 415 mBadge.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); 416 mBadge.setImageAlpha(0xef); 417 } 418 419 class Metadata { 420 private String artist; 421 private String trackTitle; 422 private String albumTitle; 423 private Bitmap bitmap; 424 private long duration; 425 426 public void clear() { 427 artist = null; 428 trackTitle = null; 429 albumTitle = null; 430 bitmap = null; 431 duration = -1; 432 } 433 434 public String toString() { 435 return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + 436 " albumTitle=" + albumTitle + " duration=" + duration + "]"; 437 } 438 } 439 440 void clearMetadata() { 441 mPopulateMetadataWhenAttached = null; 442 mMetadata.clear(); 443 populateMetadata(); 444 } 445 446 void updateMetadata(RemoteController.MetadataEditor data) { 447 if (isAttachedToWindow()) { 448 mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, 449 mMetadata.artist); 450 mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, 451 mMetadata.trackTitle); 452 mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, 453 mMetadata.albumTitle); 454 mMetadata.duration = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, -1); 455 mMetadata.bitmap = data.getBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, 456 mMetadata.bitmap); 457 populateMetadata(); 458 } else { 459 mPopulateMetadataWhenAttached = data; 460 } 461 } 462 463 /** 464 * Populates the given metadata into the view 465 */ 466 private void populateMetadata() { 467 if (ANIMATE_TRANSITIONS && isLaidOut() && mMetadataContainer.getVisibility() == VISIBLE) { 468 TransitionManager.beginDelayedTransition(mMetadataContainer, mMetadataChangeTransition); 469 } 470 471 final String remoteClientPackage = mRemoteController.getRemoteControlClientPackageName(); 472 Drawable badgeIcon = null; 473 try { 474 badgeIcon = getContext().getPackageManager().getApplicationIcon(remoteClientPackage); 475 } catch (PackageManager.NameNotFoundException e) { 476 Log.e(TAG, "Couldn't get remote control client package icon", e); 477 } 478 setBadgeIcon(badgeIcon); 479 mTrackTitle.setText(!TextUtils.isEmpty(mMetadata.trackTitle) 480 ? mMetadata.trackTitle : null); 481 482 final StringBuilder sb = new StringBuilder(); 483 if (!TextUtils.isEmpty(mMetadata.artist)) { 484 if (sb.length() != 0) { 485 sb.append(" - "); 486 } 487 sb.append(mMetadata.artist); 488 } 489 if (!TextUtils.isEmpty(mMetadata.albumTitle)) { 490 if (sb.length() != 0) { 491 sb.append(" - "); 492 } 493 sb.append(mMetadata.albumTitle); 494 } 495 496 final String trackArtistAlbum = sb.toString(); 497 mTrackArtistAlbum.setText(!TextUtils.isEmpty(trackArtistAlbum) ? 498 trackArtistAlbum : null); 499 500 if (mMetadata.duration >= 0) { 501 setSeekBarsEnabled(true); 502 setSeekBarDuration(mMetadata.duration); 503 504 final String skeleton; 505 506 if (mMetadata.duration >= 86400000) { 507 skeleton = "DDD kk mm ss"; 508 } else if (mMetadata.duration >= 3600000) { 509 skeleton = "kk mm ss"; 510 } else { 511 skeleton = "mm ss"; 512 } 513 mFormat = new SimpleDateFormat(DateFormat.getBestDateTimePattern( 514 getContext().getResources().getConfiguration().locale, 515 skeleton)); 516 mFormat.setTimeZone(TimeZone.getTimeZone("GMT+0")); 517 } else { 518 setSeekBarsEnabled(false); 519 } 520 521 KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(mMetadata.bitmap); 522 final int flags = mTransportControlFlags; 523 setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS); 524 setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT); 525 setVisibilityBasedOnFlag(mBtnPlay, flags, 526 RemoteControlClient.FLAG_KEY_MEDIA_PLAY 527 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE 528 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE 529 | RemoteControlClient.FLAG_KEY_MEDIA_STOP); 530 531 updatePlayPauseState(mCurrentPlayState); 532 } 533 534 void updateSeekDisplay() { 535 if (mMetadata != null && mRemoteController != null && mFormat != null) { 536 mTempDate.setTime(mRemoteController.getEstimatedMediaPosition()); 537 mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); 538 mTempDate.setTime(mMetadata.duration); 539 mTransientSeekTimeTotal.setText(mFormat.format(mTempDate)); 540 541 if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate + 542 " duration=" + mMetadata.duration); 543 } 544 } 545 546 boolean tryToggleSeekBar() { 547 if (ANIMATE_TRANSITIONS) { 548 TransitionManager.beginDelayedTransition(mInfoContainer); 549 } 550 if (mTransientSeek.getVisibility() == VISIBLE) { 551 mTransientSeek.setVisibility(INVISIBLE); 552 mMetadataContainer.setVisibility(VISIBLE); 553 cancelResetToMetadata(); 554 removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible 555 } else { 556 mTransientSeek.setVisibility(VISIBLE); 557 mMetadataContainer.setVisibility(INVISIBLE); 558 delayResetToMetadata(); 559 if (playbackPositionShouldMove(mCurrentPlayState)) { 560 mUpdateSeekBars.run(); 561 } else { 562 mUpdateSeekBars.updateOnce(); 563 } 564 } 565 mTransportControlCallback.userActivity(); 566 return true; 567 } 568 569 void resetToMetadata() { 570 if (ANIMATE_TRANSITIONS) { 571 TransitionManager.beginDelayedTransition(mInfoContainer); 572 } 573 if (mTransientSeek.getVisibility() == VISIBLE) { 574 mTransientSeek.setVisibility(INVISIBLE); 575 mMetadataContainer.setVisibility(VISIBLE); 576 } 577 // TODO Also hide ratings, if applicable 578 } 579 580 void delayResetToMetadata() { 581 removeCallbacks(mResetToMetadata); 582 postDelayed(mResetToMetadata, RESET_TO_METADATA_DELAY); 583 } 584 585 void cancelResetToMetadata() { 586 removeCallbacks(mResetToMetadata); 587 } 588 589 void setSeekBarDuration(long duration) { 590 mTransientSeekBar.setMax((int) duration); 591 } 592 593 void scrubTo(int progress) { 594 mRemoteController.seekTo(progress); 595 mTransportControlCallback.userActivity(); 596 } 597 598 private static void setVisibilityBasedOnFlag(View view, int flags, int flag) { 599 if ((flags & flag) != 0) { 600 view.setVisibility(View.VISIBLE); 601 } else { 602 view.setVisibility(View.INVISIBLE); 603 } 604 } 605 606 private void updatePlayPauseState(int state) { 607 if (DEBUG) Log.v(TAG, 608 "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state); 609 if (state == mCurrentPlayState) { 610 return; 611 } 612 final int imageResId; 613 final int imageDescId; 614 switch (state) { 615 case RemoteControlClient.PLAYSTATE_ERROR: 616 imageResId = R.drawable.stat_sys_warning; 617 // TODO use more specific image description string for warning, but here the "play" 618 // message is still valid because this button triggers a play command. 619 imageDescId = R.string.keyguard_transport_play_description; 620 break; 621 622 case RemoteControlClient.PLAYSTATE_PLAYING: 623 imageResId = R.drawable.ic_media_pause; 624 imageDescId = R.string.keyguard_transport_pause_description; 625 break; 626 627 case RemoteControlClient.PLAYSTATE_BUFFERING: 628 imageResId = R.drawable.ic_media_stop; 629 imageDescId = R.string.keyguard_transport_stop_description; 630 break; 631 632 case RemoteControlClient.PLAYSTATE_PAUSED: 633 default: 634 imageResId = R.drawable.ic_media_play; 635 imageDescId = R.string.keyguard_transport_play_description; 636 break; 637 } 638 639 boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0; 640 setSeekBarsEnabled(clientSupportsSeek); 641 642 mBtnPlay.setImageResource(imageResId); 643 mBtnPlay.setContentDescription(getResources().getString(imageDescId)); 644 mCurrentPlayState = state; 645 } 646 647 boolean updateSeekBars() { 648 final int position = (int) mRemoteController.getEstimatedMediaPosition(); 649 if (DEBUG) Log.v(TAG, "Estimated time:" + position); 650 if (position >= 0) { 651 mTransientSeekBar.setProgress(position); 652 return true; 653 } 654 Log.w(TAG, "Updating seek bars; received invalid estimated media position (" + 655 position + "). Disabling seek."); 656 setSeekBarsEnabled(false); 657 return false; 658 } 659 660 static class SavedState extends BaseSavedState { 661 boolean clientPresent; 662 String artist; 663 String trackTitle; 664 String albumTitle; 665 long duration; 666 Bitmap bitmap; 667 668 SavedState(Parcelable superState) { 669 super(superState); 670 } 671 672 private SavedState(Parcel in) { 673 super(in); 674 clientPresent = in.readInt() != 0; 675 artist = in.readString(); 676 trackTitle = in.readString(); 677 albumTitle = in.readString(); 678 duration = in.readLong(); 679 bitmap = Bitmap.CREATOR.createFromParcel(in); 680 } 681 682 @Override 683 public void writeToParcel(Parcel out, int flags) { 684 super.writeToParcel(out, flags); 685 out.writeInt(clientPresent ? 1 : 0); 686 out.writeString(artist); 687 out.writeString(trackTitle); 688 out.writeString(albumTitle); 689 out.writeLong(duration); 690 bitmap.writeToParcel(out, flags); 691 } 692 693 public static final Parcelable.Creator<SavedState> CREATOR 694 = new Parcelable.Creator<SavedState>() { 695 public SavedState createFromParcel(Parcel in) { 696 return new SavedState(in); 697 } 698 699 public SavedState[] newArray(int size) { 700 return new SavedState[size]; 701 } 702 }; 703 } 704 705 private void sendMediaButtonClick(int keyCode) { 706 // TODO We should think about sending these up/down events accurately with touch up/down 707 // on the buttons, but in the near term this will interfere with the long press behavior. 708 mRemoteController.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); 709 mRemoteController.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); 710 711 mTransportControlCallback.userActivity(); 712 } 713 714 public boolean providesClock() { 715 return false; 716 } 717 } 718