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