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