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