blob: 349078f2c6f82e4a062648c2e4146294d82f08a6 [file] [log] [blame]
Jim Millerff2aa0b2012-09-06 19:03:52 -07001/*
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
Jim Miller5ecd8112013-01-09 18:50:26 -080017package com.android.keyguard;
Jim Millerff2aa0b2012-09-06 19:03:52 -070018
Jim Millerff2aa0b2012-09-06 19:03:52 -070019import android.content.Context;
Adam Powellf8895242013-09-30 16:16:24 -070020import android.content.pm.PackageManager;
21import android.content.res.Configuration;
Jim Millerff2aa0b2012-09-06 19:03:52 -070022import android.graphics.Bitmap;
Adam Powellf8895242013-09-30 16:16:24 -070023import android.graphics.ColorMatrix;
24import android.graphics.ColorMatrixColorFilter;
Adam Powell31049d72013-10-07 12:58:42 -070025import android.graphics.PorterDuff;
26import android.graphics.PorterDuffXfermode;
Adam Powellf8895242013-09-30 16:16:24 -070027import android.graphics.drawable.Drawable;
Jim Millerff2aa0b2012-09-06 19:03:52 -070028import android.media.AudioManager;
Adam Powellf8895242013-09-30 16:16:24 -070029import android.media.MediaMetadataEditor;
Jim Millerff2aa0b2012-09-06 19:03:52 -070030import android.media.MediaMetadataRetriever;
31import android.media.RemoteControlClient;
Adam Powellf8895242013-09-30 16:16:24 -070032import android.media.RemoteController;
Jim Millerff2aa0b2012-09-06 19:03:52 -070033import android.os.Parcel;
34import android.os.Parcelable;
Jim Millerff2aa0b2012-09-06 19:03:52 -070035import android.os.SystemClock;
Jim Millerff2aa0b2012-09-06 19:03:52 -070036import android.text.TextUtils;
Adam Powellf8895242013-09-30 16:16:24 -070037import android.text.format.DateFormat;
38import android.transition.ChangeBounds;
39import android.transition.ChangeText;
40import android.transition.Fade;
41import android.transition.TransitionManager;
42import android.transition.TransitionSet;
Jim Millerff2aa0b2012-09-06 19:03:52 -070043import android.util.AttributeSet;
Adam Powell43a372f2013-09-27 17:43:53 -070044import android.util.DisplayMetrics;
Jim Millerff2aa0b2012-09-06 19:03:52 -070045import android.util.Log;
46import android.view.KeyEvent;
47import android.view.View;
Adam Powellf8895242013-09-30 16:16:24 -070048import android.view.ViewGroup;
Jim Millerbdca3c02012-10-29 19:11:50 -070049import android.widget.FrameLayout;
Jim Millerff2aa0b2012-09-06 19:03:52 -070050import android.widget.ImageView;
Adam Powellf8895242013-09-30 16:16:24 -070051import android.widget.SeekBar;
Jim Millerff2aa0b2012-09-06 19:03:52 -070052import android.widget.TextView;
53
Adam Powellf8895242013-09-30 16:16:24 -070054import java.text.SimpleDateFormat;
55import java.util.Date;
56import java.util.TimeZone;
57
Jim Millerff2aa0b2012-09-06 19:03:52 -070058/**
59 * This is the widget responsible for showing music controls in keyguard.
60 */
Adam Powellf8895242013-09-30 16:16:24 -070061public class KeyguardTransportControlView extends FrameLayout {
Jim Millerff2aa0b2012-09-06 19:03:52 -070062
Adam Powellf8895242013-09-30 16:16:24 -070063 private static final int RESET_TO_METADATA_DELAY = 5000;
Jim Miller71b3cd52012-10-10 19:02:40 -070064 protected static final boolean DEBUG = false;
Jim Millerff2aa0b2012-09-06 19:03:52 -070065 protected static final String TAG = "TransportControlView";
66
Chet Haasedf32aa82013-10-21 17:19:37 -070067 private static final boolean ANIMATE_TRANSITIONS = true;
Jim Millercc747ad2013-10-24 19:53:09 -070068 protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000;
Adam Powellf8895242013-09-30 16:16:24 -070069
70 private ViewGroup mMetadataContainer;
71 private ViewGroup mInfoContainer;
Jim Millerff2aa0b2012-09-06 19:03:52 -070072 private TextView mTrackTitle;
Adam Powellf8895242013-09-30 16:16:24 -070073 private TextView mTrackArtistAlbum;
74
75 private View mTransientSeek;
76 private SeekBar mTransientSeekBar;
77 private TextView mTransientSeekTimeElapsed;
Chris Wren6af85cc2013-10-29 13:19:54 -040078 private TextView mTransientSeekTimeTotal;
Adam Powellf8895242013-09-30 16:16:24 -070079
Jim Millerff2aa0b2012-09-06 19:03:52 -070080 private ImageView mBtnPrev;
81 private ImageView mBtnPlay;
82 private ImageView mBtnNext;
Jim Millerff2aa0b2012-09-06 19:03:52 -070083 private Metadata mMetadata = new Metadata();
Jim Millerff2aa0b2012-09-06 19:03:52 -070084 private int mTransportControlFlags;
85 private int mCurrentPlayState;
86 private AudioManager mAudioManager;
Adam Powellf8895242013-09-30 16:16:24 -070087 private RemoteController mRemoteController;
88
89 private ImageView mBadge;
90
91 private boolean mSeekEnabled;
Adam Powellf8895242013-09-30 16:16:24 -070092 private java.text.DateFormat mFormat;
Jim Millerff2aa0b2012-09-06 19:03:52 -070093
Jim Millercc747ad2013-10-24 19:53:09 -070094 private Date mTempDate = new Date();
Chris Wrencab69652013-10-29 13:04:00 -040095
Jim Millerff2aa0b2012-09-06 19:03:52 -070096 /**
97 * The metadata which should be populated into the view once we've been attached
98 */
Adam Powellf8895242013-09-30 16:16:24 -070099 private RemoteController.MetadataEditor mPopulateMetadataWhenAttached = null;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700100
Adam Powellf8895242013-09-30 16:16:24 -0700101 private RemoteController.OnClientUpdateListener mRCClientUpdateListener =
102 new RemoteController.OnClientUpdateListener() {
Jim Millerff2aa0b2012-09-06 19:03:52 -0700103 @Override
Adam Powellf8895242013-09-30 16:16:24 -0700104 public void onClientChange(boolean clearing) {
Craig Mautner83341922013-10-30 10:29:38 -0700105 if (clearing) {
106 clearMetadata();
107 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700108 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700109
Adam Powellf8895242013-09-30 16:16:24 -0700110 @Override
111 public void onClientPlaybackStateUpdate(int state) {
Adam Powellf8895242013-09-30 16:16:24 -0700112 updatePlayPauseState(state);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700113 }
114
Adam Powellf8895242013-09-30 16:16:24 -0700115 @Override
116 public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
Jean-Michel Trivibc43b4c2013-03-22 09:30:50 -0700117 long currentPosMs, float speed) {
Adam Powellf8895242013-09-30 16:16:24 -0700118 updatePlayPauseState(state);
119 if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
120 ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
121 ", speed=" + speed + ")");
Jim Millercc747ad2013-10-24 19:53:09 -0700122
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 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700131 }
132
Adam Powellf8895242013-09-30 16:16:24 -0700133 @Override
134 public void onClientTransportControlUpdate(int transportControlFlags) {
135 updateTransportControls(transportControlFlags);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700136 }
137
Adam Powellf8895242013-09-30 16:16:24 -0700138 @Override
139 public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
140 updateMetadata(metadataEditor);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700141 }
Adam Powellf8895242013-09-30 16:16:24 -0700142 };
Jim Millerff2aa0b2012-09-06 19:03:52 -0700143
Jim Millercc747ad2013-10-24 19:53:09 -0700144 private class UpdateSeekBarRunnable implements Runnable {
Adam Powellf8895242013-09-30 16:16:24 -0700145 public void run() {
Jim Millercc747ad2013-10-24 19:53:09 -0700146 boolean seekAble = updateOnce();
147 if (seekAble) {
Jim Miller1693f6a2013-10-20 17:21:57 -0700148 removeCallbacks(this);
Adam Powellf8895242013-09-30 16:16:24 -0700149 postDelayed(this, 1000);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700150 }
151 }
Jim Millercc747ad2013-10-24 19:53:09 -0700152 public boolean updateOnce() {
153 return updateSeekBars();
154 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700155 };
156
Jim Millercc747ad2013-10-24 19:53:09 -0700157 private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable();
158
Adam Powellf8895242013-09-30 16:16:24 -0700159 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);
Jim Millercc747ad2013-10-24 19:53:09 -0700177 delayResetToMetadata(); // if the scrub bar is showing, keep showing it.
Adam Powellf8895242013-09-30 16:16:24 -0700178 }
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
Jim Millercc747ad2013-10-24 19:53:09 -0700192 // 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
Adam Powellf8895242013-09-30 16:16:24 -0700231 private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
232 new SeekBar.OnSeekBarChangeListener() {
233 @Override
234 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
235 if (fromUser) {
Jim Millercc747ad2013-10-24 19:53:09 -0700236 mFutureSeekRunnable.setProgress(progress);
Adam Powellf8895242013-09-30 16:16:24 -0700237 delayResetToMetadata();
Jim Millercc747ad2013-10-24 19:53:09 -0700238 mTempDate.setTime(progress);
239 mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
240 } else {
241 updateSeekDisplay();
Adam Powellf8895242013-09-30 16:16:24 -0700242 }
Adam Powellf8895242013-09-30 16:16:24 -0700243 }
244
245 @Override
246 public void onStartTrackingTouch(SeekBar seekBar) {
Jim Millercc747ad2013-10-24 19:53:09 -0700247 delayResetToMetadata();
248 removeCallbacks(mUpdateSeekBars); // don't update during user interaction
Adam Powellf8895242013-09-30 16:16:24 -0700249 }
250
251 @Override
252 public void onStopTrackingTouch(SeekBar seekBar) {
Adam Powellf8895242013-09-30 16:16:24 -0700253 }
254 };
255
256 private static final int TRANSITION_DURATION = 200;
257 private final TransitionSet mMetadataChangeTransition;
258
259 KeyguardHostView.TransportControlCallback mTransportControlCallback;
260
Jim Miller20daffd2013-10-07 14:59:53 -0700261 private final KeyguardUpdateMonitorCallback mUpdateMonitor
262 = new KeyguardUpdateMonitorCallback() {
263 public void onScreenTurnedOff(int why) {
264 setEnableMarquee(false);
Craig Mautner83341922013-10-30 10:29:38 -0700265 }
Jim Miller20daffd2013-10-07 14:59:53 -0700266 public void onScreenTurnedOn() {
267 setEnableMarquee(true);
Craig Mautner83341922013-10-30 10:29:38 -0700268 }
Jim Miller20daffd2013-10-07 14:59:53 -0700269 };
270
Jim Millerff2aa0b2012-09-06 19:03:52 -0700271 public KeyguardTransportControlView(Context context, AttributeSet attrs) {
272 super(context, attrs);
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800273 if (DEBUG) Log.v(TAG, "Create TCV " + this);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700274 mAudioManager = new AudioManager(mContext);
275 mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700276 mRemoteController = new RemoteController(context, mRCClientUpdateListener);
Adam Powellf8895242013-09-30 16:16:24 -0700277
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;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700292 }
293
Jim Millerff2aa0b2012-09-06 19:03:52 -0700294 private void updateTransportControls(int transportControlFlags) {
295 mTransportControlFlags = transportControlFlags;
Adam Powellf8895242013-09-30 16:16:24 -0700296 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;
Jim Millercc747ad2013-10-24 19:53:09 -0700304 if (mTransientSeek.getVisibility() == VISIBLE && !enabled) {
Adam Powellf8895242013-09-30 16:16:24 -0700305 mTransientSeek.setVisibility(INVISIBLE);
306 mMetadataContainer.setVisibility(VISIBLE);
Adam Powellf8895242013-09-30 16:16:24 -0700307 cancelResetToMetadata();
308 }
Adam Powellf8895242013-09-30 16:16:24 -0700309 }
310
311 public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
312 transportControlCallback) {
313 mTransportControlCallback = transportControlCallback;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700314 }
315
Jim Miller20daffd2013-10-07 14:59:53 -0700316 private void setEnableMarquee(boolean enabled) {
317 if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
Adam Powell31049d72013-10-07 12:58:42 -0700318 if (mTrackTitle != null) mTrackTitle.setSelected(enabled);
319 if (mTrackArtistAlbum != null) mTrackTitle.setSelected(enabled);
Jim Miller20daffd2013-10-07 14:59:53 -0700320 }
321
Jim Millerff2aa0b2012-09-06 19:03:52 -0700322 @Override
323 public void onFinishInflate() {
324 super.onFinishInflate();
Adam Powellf8895242013-09-30 16:16:24 -0700325 mInfoContainer = (ViewGroup) findViewById(R.id.info_container);
326 mMetadataContainer = (ViewGroup) findViewById(R.id.metadata_container);
327 mBadge = (ImageView) findViewById(R.id.badge);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700328 mTrackTitle = (TextView) findViewById(R.id.title);
Adam Powellf8895242013-09-30 16:16:24 -0700329 mTrackArtistAlbum = (TextView) findViewById(R.id.artist_album);
Adam Powellf8895242013-09-30 16:16:24 -0700330 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);
Chris Wren6af85cc2013-10-29 13:19:54 -0400334 mTransientSeekTimeTotal = (TextView) findViewById(R.id.transient_seek_time_remaining);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700335 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) {
Adam Powellf8895242013-09-30 16:16:24 -0700340 view.setOnClickListener(mTransportCommandListener);
341 view.setOnLongClickListener(mTransportShowSeekBarListener);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700342 }
Jim Miller20daffd2013-10-07 14:59:53 -0700343 final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
344 setEnableMarquee(screenOn);
Jim Millercc747ad2013-10-24 19:53:09 -0700345 // Allow long-press anywhere else in this view to show the seek bar
346 setOnLongClickListener(mTransportShowSeekBarListener);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700347 }
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 }
Adam Powellf8895242013-09-30 16:16:24 -0700357 if (DEBUG) Log.v(TAG, "Registering TCV " + this);
Adam Powell3b435fb2013-10-15 19:15:39 -0700358 mMetadata.clear();
Adam Powellf8895242013-09-30 16:16:24 -0700359 mAudioManager.registerRemoteController(mRemoteController);
Jim Miller20daffd2013-10-07 14:59:53 -0700360 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitor);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700361 }
362
363 @Override
Adam Powellf8895242013-09-30 16:16:24 -0700364 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);
Jean-Michel Trivi9e589b92013-03-08 14:30:10 -0800369 }
370
371 @Override
Jim Millerff2aa0b2012-09-06 19:03:52 -0700372 public void onDetachedFromWindow() {
373 if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
374 super.onDetachedFromWindow();
Adam Powellf8895242013-09-30 16:16:24 -0700375 if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
376 mAudioManager.unregisterRemoteController(mRemoteController);
Jim Miller20daffd2013-10-07 14:59:53 -0700377 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
Adam Powell3b435fb2013-10-15 19:15:39 -0700378 mMetadata.clear();
Jim Millerb2c64692013-10-15 20:09:07 -0700379 removeCallbacks(mUpdateSeekBars);
Adam Powellf8895242013-09-30 16:16:24 -0700380 }
381
Craig Mautner83341922013-10-30 10:29:38 -0700382 @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
Adam Powellf8895242013-09-30 16:16:24 -0700409 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));
Adam Powell31049d72013-10-07 12:58:42 -0700415 mBadge.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
Adam Powellf8895242013-09-30 16:16:24 -0700416 mBadge.setImageAlpha(0xef);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700417 }
418
Jim Millerff2aa0b2012-09-06 19:03:52 -0700419 class Metadata {
420 private String artist;
421 private String trackTitle;
422 private String albumTitle;
423 private Bitmap bitmap;
Adam Powellf8895242013-09-30 16:16:24 -0700424 private long duration;
425
426 public void clear() {
427 artist = null;
428 trackTitle = null;
429 albumTitle = null;
430 bitmap = null;
431 duration = -1;
432 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700433
434 public String toString() {
Adam Powellf8895242013-09-30 16:16:24 -0700435 return "Metadata[artist=" + artist + " trackTitle=" + trackTitle +
436 " albumTitle=" + albumTitle + " duration=" + duration + "]";
Jim Millerff2aa0b2012-09-06 19:03:52 -0700437 }
438 }
439
Adam Powellf8895242013-09-30 16:16:24 -0700440 void clearMetadata() {
441 mPopulateMetadataWhenAttached = null;
442 mMetadata.clear();
443 populateMetadata();
Jim Millerff2aa0b2012-09-06 19:03:52 -0700444 }
445
Adam Powellf8895242013-09-30 16:16:24 -0700446 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);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700457 populateMetadata();
458 } else {
459 mPopulateMetadataWhenAttached = data;
460 }
461 }
462
463 /**
464 * Populates the given metadata into the view
465 */
466 private void populateMetadata() {
Adam Powellf8895242013-09-30 16:16:24 -0700467 if (ANIMATE_TRANSITIONS && isLaidOut() && mMetadataContainer.getVisibility() == VISIBLE) {
468 TransitionManager.beginDelayedTransition(mMetadataContainer, mMetadataChangeTransition);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700469 }
Adam Powellf8895242013-09-30 16:16:24 -0700470
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);
Adam Powell3b435fb2013-10-15 19:15:39 -0700479 mTrackTitle.setText(!TextUtils.isEmpty(mMetadata.trackTitle)
480 ? mMetadata.trackTitle : null);
481
482 final StringBuilder sb = new StringBuilder();
Jim Millerff2aa0b2012-09-06 19:03:52 -0700483 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 }
Adam Powell3b435fb2013-10-15 19:15:39 -0700495
496 final String trackArtistAlbum = sb.toString();
497 mTrackArtistAlbum.setText(!TextUtils.isEmpty(trackArtistAlbum) ?
498 trackArtistAlbum : null);
Adam Powellf8895242013-09-30 16:16:24 -0700499
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);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700519 }
520
Jim Miller1693f6a2013-10-20 17:21:57 -0700521 KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(mMetadata.bitmap);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700522 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
Adam Powellf8895242013-09-30 16:16:24 -0700534 void updateSeekDisplay() {
535 if (mMetadata != null && mRemoteController != null && mFormat != null) {
Jim Millercc747ad2013-10-24 19:53:09 -0700536 mTempDate.setTime(mRemoteController.getEstimatedMediaPosition());
537 mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
538 mTempDate.setTime(mMetadata.duration);
539 mTransientSeekTimeTotal.setText(mFormat.format(mTempDate));
Adam Powellf8895242013-09-30 16:16:24 -0700540
Jim Millercc747ad2013-10-24 19:53:09 -0700541 if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate +
Chris Wren6af85cc2013-10-29 13:19:54 -0400542 " duration=" + mMetadata.duration);
Adam Powellf8895242013-09-30 16:16:24 -0700543 }
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();
Jim Millercc747ad2013-10-24 19:53:09 -0700554 removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible
Adam Powellf8895242013-09-30 16:16:24 -0700555 } else {
556 mTransientSeek.setVisibility(VISIBLE);
557 mMetadataContainer.setVisibility(INVISIBLE);
558 delayResetToMetadata();
Jim Millercc747ad2013-10-24 19:53:09 -0700559 if (playbackPositionShouldMove(mCurrentPlayState)) {
560 mUpdateSeekBars.run();
561 } else {
562 mUpdateSeekBars.updateOnce();
563 }
Adam Powellf8895242013-09-30 16:16:24 -0700564 }
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
Jim Millerff2aa0b2012-09-06 19:03:52 -0700598 private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
599 if ((flags & flag) != 0) {
600 view.setVisibility(View.VISIBLE);
601 } else {
Adam Powellf5176922013-10-13 10:39:04 -0700602 view.setVisibility(View.INVISIBLE);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700603 }
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;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700614 switch (state) {
615 case RemoteControlClient.PLAYSTATE_ERROR:
Jim Miller5ecd8112013-01-09 18:50:26 -0800616 imageResId = R.drawable.stat_sys_warning;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700617 // 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.
Jim Miller5ecd8112013-01-09 18:50:26 -0800619 imageDescId = R.string.keyguard_transport_play_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700620 break;
621
622 case RemoteControlClient.PLAYSTATE_PLAYING:
Jim Miller5ecd8112013-01-09 18:50:26 -0800623 imageResId = R.drawable.ic_media_pause;
624 imageDescId = R.string.keyguard_transport_pause_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700625 break;
626
627 case RemoteControlClient.PLAYSTATE_BUFFERING:
Jim Miller5ecd8112013-01-09 18:50:26 -0800628 imageResId = R.drawable.ic_media_stop;
629 imageDescId = R.string.keyguard_transport_stop_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700630 break;
631
632 case RemoteControlClient.PLAYSTATE_PAUSED:
633 default:
Jim Miller5ecd8112013-01-09 18:50:26 -0800634 imageResId = R.drawable.ic_media_play;
635 imageDescId = R.string.keyguard_transport_play_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700636 break;
637 }
Adam Powellf8895242013-09-30 16:16:24 -0700638
Jim Millercc747ad2013-10-24 19:53:09 -0700639 boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0;
640 setSeekBarsEnabled(clientSupportsSeek);
641
Jim Millerff2aa0b2012-09-06 19:03:52 -0700642 mBtnPlay.setImageResource(imageResId);
643 mBtnPlay.setContentDescription(getResources().getString(imageDescId));
Jim Millerff2aa0b2012-09-06 19:03:52 -0700644 mCurrentPlayState = state;
645 }
646
Adam Powellf8895242013-09-30 16:16:24 -0700647 boolean updateSeekBars() {
648 final int position = (int) mRemoteController.getEstimatedMediaPosition();
Jim Millercc747ad2013-10-24 19:53:09 -0700649 if (DEBUG) Log.v(TAG, "Estimated time:" + position);
Adam Powellf8895242013-09-30 16:16:24 -0700650 if (position >= 0) {
Jim Millercc747ad2013-10-24 19:53:09 -0700651 mTransientSeekBar.setProgress(position);
Adam Powellf8895242013-09-30 16:16:24 -0700652 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
Jim Millerff2aa0b2012-09-06 19:03:52 -0700660 static class SavedState extends BaseSavedState {
Jim Miller223ce5c2012-10-05 19:13:23 -0700661 boolean clientPresent;
Craig Mautner83341922013-10-30 10:29:38 -0700662 String artist;
663 String trackTitle;
664 String albumTitle;
665 long duration;
666 Bitmap bitmap;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700667
668 SavedState(Parcelable superState) {
669 super(superState);
670 }
671
672 private SavedState(Parcel in) {
673 super(in);
Craig Mautner83341922013-10-30 10:29:38 -0700674 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);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700680 }
681
682 @Override
683 public void writeToParcel(Parcel out, int flags) {
684 super.writeToParcel(out, flags);
Craig Mautner83341922013-10-30 10:29:38 -0700685 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);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700691 }
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
Jim Millerff2aa0b2012-09-06 19:03:52 -0700705 private void sendMediaButtonClick(int keyCode) {
Adam Powellf8895242013-09-30 16:16:24 -0700706 // 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));
Jim Millerff2aa0b2012-09-06 19:03:52 -0700710
Adam Powellf8895242013-09-30 16:16:24 -0700711 mTransportControlCallback.userActivity();
Jim Millerff2aa0b2012-09-06 19:03:52 -0700712 }
713
714 public boolean providesClock() {
715 return false;
716 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700717}