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