Home | History | Annotate | Download | only in keyguard
      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 
     17 package com.android.keyguard;
     18 
     19 import android.content.Context;
     20 import android.content.pm.PackageManager;
     21 import android.content.res.Configuration;
     22 import android.graphics.Bitmap;
     23 import android.graphics.ColorMatrix;
     24 import android.graphics.ColorMatrixColorFilter;
     25 import android.graphics.PorterDuff;
     26 import android.graphics.PorterDuffXfermode;
     27 import android.graphics.drawable.Drawable;
     28 import android.media.AudioManager;
     29 import android.media.MediaMetadataEditor;
     30 import android.media.MediaMetadataRetriever;
     31 import android.media.RemoteControlClient;
     32 import android.media.RemoteController;
     33 import android.os.Parcel;
     34 import android.os.Parcelable;
     35 import android.os.SystemClock;
     36 import android.text.TextUtils;
     37 import android.text.format.DateFormat;
     38 import android.transition.ChangeBounds;
     39 import android.transition.ChangeText;
     40 import android.transition.Fade;
     41 import android.transition.TransitionManager;
     42 import android.transition.TransitionSet;
     43 import android.util.AttributeSet;
     44 import android.util.DisplayMetrics;
     45 import android.util.Log;
     46 import android.view.KeyEvent;
     47 import android.view.View;
     48 import android.view.ViewGroup;
     49 import android.widget.FrameLayout;
     50 import android.widget.ImageView;
     51 import android.widget.SeekBar;
     52 import android.widget.TextView;
     53 
     54 import java.text.SimpleDateFormat;
     55 import java.util.Date;
     56 import java.util.TimeZone;
     57 
     58 /**
     59  * This is the widget responsible for showing music controls in keyguard.
     60  */
     61 public 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