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