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