blob: a7cd9097bfdbb0b519766d7fe21dea8447dd40de [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.text.TextUtils;
Adam Powellf8895242013-09-30 16:16:24 -070036import android.text.format.DateFormat;
37import android.transition.ChangeBounds;
38import android.transition.ChangeText;
39import android.transition.Fade;
40import android.transition.TransitionManager;
41import android.transition.TransitionSet;
Jim Millerff2aa0b2012-09-06 19:03:52 -070042import android.util.AttributeSet;
Adam Powell43a372f2013-09-27 17:43:53 -070043import android.util.DisplayMetrics;
Jim Millerff2aa0b2012-09-06 19:03:52 -070044import android.util.Log;
45import android.view.KeyEvent;
46import android.view.View;
Adam Powellf8895242013-09-30 16:16:24 -070047import android.view.ViewGroup;
Jim Millerbdca3c02012-10-29 19:11:50 -070048import android.widget.FrameLayout;
Jim Millerff2aa0b2012-09-06 19:03:52 -070049import android.widget.ImageView;
Adam Powellf8895242013-09-30 16:16:24 -070050import android.widget.SeekBar;
Jim Millerff2aa0b2012-09-06 19:03:52 -070051import android.widget.TextView;
52
Adam Powellf8895242013-09-30 16:16:24 -070053import java.text.SimpleDateFormat;
54import java.util.Date;
55import java.util.TimeZone;
56
Jim Millerff2aa0b2012-09-06 19:03:52 -070057/**
58 * This is the widget responsible for showing music controls in keyguard.
59 */
Adam Powellf8895242013-09-30 16:16:24 -070060public class KeyguardTransportControlView extends FrameLayout {
Jim Millerff2aa0b2012-09-06 19:03:52 -070061
Adam Powellf8895242013-09-30 16:16:24 -070062 private static final int RESET_TO_METADATA_DELAY = 5000;
Jim Miller71b3cd52012-10-10 19:02:40 -070063 protected static final boolean DEBUG = false;
Jim Millerff2aa0b2012-09-06 19:03:52 -070064 protected static final String TAG = "TransportControlView";
65
Chet Haasedf32aa82013-10-21 17:19:37 -070066 private static final boolean ANIMATE_TRANSITIONS = true;
Jim Millercc747ad2013-10-24 19:53:09 -070067 protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000;
Adam Powellf8895242013-09-30 16:16:24 -070068
69 private ViewGroup mMetadataContainer;
70 private ViewGroup mInfoContainer;
Jim Millerff2aa0b2012-09-06 19:03:52 -070071 private TextView mTrackTitle;
Adam Powellf8895242013-09-30 16:16:24 -070072 private TextView mTrackArtistAlbum;
73
74 private View mTransientSeek;
75 private SeekBar mTransientSeekBar;
76 private TextView mTransientSeekTimeElapsed;
Chris Wren6af85cc2013-10-29 13:19:54 -040077 private TextView mTransientSeekTimeTotal;
Adam Powellf8895242013-09-30 16:16:24 -070078
Jim Millerff2aa0b2012-09-06 19:03:52 -070079 private ImageView mBtnPrev;
80 private ImageView mBtnPlay;
81 private ImageView mBtnNext;
Jim Millerff2aa0b2012-09-06 19:03:52 -070082 private Metadata mMetadata = new Metadata();
Jim Millerff2aa0b2012-09-06 19:03:52 -070083 private int mTransportControlFlags;
84 private int mCurrentPlayState;
85 private AudioManager mAudioManager;
Adam Powellf8895242013-09-30 16:16:24 -070086 private RemoteController mRemoteController;
87
88 private ImageView mBadge;
89
90 private boolean mSeekEnabled;
Adam Powellf8895242013-09-30 16:16:24 -070091 private java.text.DateFormat mFormat;
Jim Millerff2aa0b2012-09-06 19:03:52 -070092
Jim Millercc747ad2013-10-24 19:53:09 -070093 private Date mTempDate = new Date();
Chris Wrencab69652013-10-29 13:04:00 -040094
Jim Millerff2aa0b2012-09-06 19:03:52 -070095 /**
96 * The metadata which should be populated into the view once we've been attached
97 */
Adam Powellf8895242013-09-30 16:16:24 -070098 private RemoteController.MetadataEditor mPopulateMetadataWhenAttached = null;
Jim Millerff2aa0b2012-09-06 19:03:52 -070099
Adam Powellf8895242013-09-30 16:16:24 -0700100 private RemoteController.OnClientUpdateListener mRCClientUpdateListener =
101 new RemoteController.OnClientUpdateListener() {
Jim Millerff2aa0b2012-09-06 19:03:52 -0700102 @Override
Adam Powellf8895242013-09-30 16:16:24 -0700103 public void onClientChange(boolean clearing) {
Craig Mautner83341922013-10-30 10:29:38 -0700104 if (clearing) {
105 clearMetadata();
106 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700107 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700108
Adam Powellf8895242013-09-30 16:16:24 -0700109 @Override
110 public void onClientPlaybackStateUpdate(int state) {
Adam Powellf8895242013-09-30 16:16:24 -0700111 updatePlayPauseState(state);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700112 }
113
Adam Powellf8895242013-09-30 16:16:24 -0700114 @Override
115 public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
Jean-Michel Trivibc43b4c2013-03-22 09:30:50 -0700116 long currentPosMs, float speed) {
Adam Powellf8895242013-09-30 16:16:24 -0700117 updatePlayPauseState(state);
118 if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
119 ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
120 ", speed=" + speed + ")");
Jim Millercc747ad2013-10-24 19:53:09 -0700121
122 removeCallbacks(mUpdateSeekBars);
123 // Since the music client may be responding to historical events that cause the
124 // playback state to change dramatically, wait until things become quiescent before
125 // resuming automatic scrub position update.
126 if (mTransientSeek.getVisibility() == View.VISIBLE
127 && playbackPositionShouldMove(mCurrentPlayState)) {
128 postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR);
129 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700130 }
131
Adam Powellf8895242013-09-30 16:16:24 -0700132 @Override
133 public void onClientTransportControlUpdate(int transportControlFlags) {
134 updateTransportControls(transportControlFlags);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700135 }
136
Adam Powellf8895242013-09-30 16:16:24 -0700137 @Override
138 public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
139 updateMetadata(metadataEditor);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700140 }
Adam Powellf8895242013-09-30 16:16:24 -0700141 };
Jim Millerff2aa0b2012-09-06 19:03:52 -0700142
Jim Millercc747ad2013-10-24 19:53:09 -0700143 private class UpdateSeekBarRunnable implements Runnable {
Adam Powellf8895242013-09-30 16:16:24 -0700144 public void run() {
Jim Millercc747ad2013-10-24 19:53:09 -0700145 boolean seekAble = updateOnce();
146 if (seekAble) {
Jim Miller1693f6a2013-10-20 17:21:57 -0700147 removeCallbacks(this);
Adam Powellf8895242013-09-30 16:16:24 -0700148 postDelayed(this, 1000);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700149 }
150 }
Jim Millercc747ad2013-10-24 19:53:09 -0700151 public boolean updateOnce() {
152 return updateSeekBars();
153 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700154 };
155
Jim Millercc747ad2013-10-24 19:53:09 -0700156 private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable();
157
Adam Powellf8895242013-09-30 16:16:24 -0700158 private final Runnable mResetToMetadata = new Runnable() {
159 public void run() {
160 resetToMetadata();
161 }
162 };
163
164 private final OnClickListener mTransportCommandListener = new OnClickListener() {
165 public void onClick(View v) {
166 int keyCode = -1;
167 if (v == mBtnPrev) {
168 keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
169 } else if (v == mBtnNext) {
170 keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
171 } else if (v == mBtnPlay) {
172 keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
173 }
174 if (keyCode != -1) {
175 sendMediaButtonClick(keyCode);
Jim Millercc747ad2013-10-24 19:53:09 -0700176 delayResetToMetadata(); // if the scrub bar is showing, keep showing it.
Adam Powellf8895242013-09-30 16:16:24 -0700177 }
178 }
179 };
180
181 private final OnLongClickListener mTransportShowSeekBarListener = new OnLongClickListener() {
182 @Override
183 public boolean onLongClick(View v) {
184 if (mSeekEnabled) {
185 return tryToggleSeekBar();
186 }
187 return false;
188 }
189 };
190
Jim Millercc747ad2013-10-24 19:53:09 -0700191 // This class is here to throttle scrub position updates to the music client
192 class FutureSeekRunnable implements Runnable {
193 private int mProgress;
194 private boolean mPending;
195
196 public void run() {
197 scrubTo(mProgress);
198 mPending = false;
199 }
200
201 void setProgress(int progress) {
202 mProgress = progress;
203 if (!mPending) {
204 mPending = true;
205 postDelayed(this, 30);
206 }
207 }
208 };
209
210 // This is here because RemoteControlClient's method isn't visible :/
211 private final static boolean playbackPositionShouldMove(int playstate) {
212 switch(playstate) {
213 case RemoteControlClient.PLAYSTATE_STOPPED:
214 case RemoteControlClient.PLAYSTATE_PAUSED:
215 case RemoteControlClient.PLAYSTATE_BUFFERING:
216 case RemoteControlClient.PLAYSTATE_ERROR:
217 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
218 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
219 return false;
220 case RemoteControlClient.PLAYSTATE_PLAYING:
221 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
222 case RemoteControlClient.PLAYSTATE_REWINDING:
223 default:
224 return true;
225 }
226 }
227
228 private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable();
229
Adam Powellf8895242013-09-30 16:16:24 -0700230 private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
231 new SeekBar.OnSeekBarChangeListener() {
232 @Override
233 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
234 if (fromUser) {
Jim Millercc747ad2013-10-24 19:53:09 -0700235 mFutureSeekRunnable.setProgress(progress);
Adam Powellf8895242013-09-30 16:16:24 -0700236 delayResetToMetadata();
Jim Millercc747ad2013-10-24 19:53:09 -0700237 mTempDate.setTime(progress);
238 mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
239 } else {
240 updateSeekDisplay();
Adam Powellf8895242013-09-30 16:16:24 -0700241 }
Adam Powellf8895242013-09-30 16:16:24 -0700242 }
243
244 @Override
245 public void onStartTrackingTouch(SeekBar seekBar) {
Jim Millercc747ad2013-10-24 19:53:09 -0700246 delayResetToMetadata();
247 removeCallbacks(mUpdateSeekBars); // don't update during user interaction
Adam Powellf8895242013-09-30 16:16:24 -0700248 }
249
250 @Override
251 public void onStopTrackingTouch(SeekBar seekBar) {
Adam Powellf8895242013-09-30 16:16:24 -0700252 }
253 };
254
255 private static final int TRANSITION_DURATION = 200;
256 private final TransitionSet mMetadataChangeTransition;
257
258 KeyguardHostView.TransportControlCallback mTransportControlCallback;
259
Jim Miller20daffd2013-10-07 14:59:53 -0700260 private final KeyguardUpdateMonitorCallback mUpdateMonitor
261 = new KeyguardUpdateMonitorCallback() {
262 public void onScreenTurnedOff(int why) {
263 setEnableMarquee(false);
Craig Mautner83341922013-10-30 10:29:38 -0700264 }
Jim Miller20daffd2013-10-07 14:59:53 -0700265 public void onScreenTurnedOn() {
266 setEnableMarquee(true);
Craig Mautner83341922013-10-30 10:29:38 -0700267 }
Jim Miller20daffd2013-10-07 14:59:53 -0700268 };
269
Jim Millerff2aa0b2012-09-06 19:03:52 -0700270 public KeyguardTransportControlView(Context context, AttributeSet attrs) {
271 super(context, attrs);
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800272 if (DEBUG) Log.v(TAG, "Create TCV " + this);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700273 mAudioManager = new AudioManager(mContext);
274 mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700275 mRemoteController = new RemoteController(context, mRCClientUpdateListener);
Adam Powellf8895242013-09-30 16:16:24 -0700276
277 final DisplayMetrics dm = context.getResources().getDisplayMetrics();
278 final int dim = Math.max(dm.widthPixels, dm.heightPixels);
279 mRemoteController.setArtworkConfiguration(true, dim, dim);
280
281 final ChangeText tc = new ChangeText();
282 tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
283 final TransitionSet inner = new TransitionSet();
284 inner.addTransition(tc).addTransition(new ChangeBounds());
285 final TransitionSet tg = new TransitionSet();
286 tg.addTransition(new Fade(Fade.OUT)).addTransition(inner).
287 addTransition(new Fade(Fade.IN));
288 tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
289 tg.setDuration(TRANSITION_DURATION);
290 mMetadataChangeTransition = tg;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700291 }
292
Jim Millerff2aa0b2012-09-06 19:03:52 -0700293 private void updateTransportControls(int transportControlFlags) {
294 mTransportControlFlags = transportControlFlags;
Adam Powellf8895242013-09-30 16:16:24 -0700295 setSeekBarsEnabled(
296 (transportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE) != 0);
297 }
298
299 void setSeekBarsEnabled(boolean enabled) {
300 if (enabled == mSeekEnabled) return;
301
302 mSeekEnabled = enabled;
Jim Millercc747ad2013-10-24 19:53:09 -0700303 if (mTransientSeek.getVisibility() == VISIBLE && !enabled) {
Adam Powellf8895242013-09-30 16:16:24 -0700304 mTransientSeek.setVisibility(INVISIBLE);
305 mMetadataContainer.setVisibility(VISIBLE);
Adam Powellf8895242013-09-30 16:16:24 -0700306 cancelResetToMetadata();
307 }
Adam Powellf8895242013-09-30 16:16:24 -0700308 }
309
310 public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
311 transportControlCallback) {
312 mTransportControlCallback = transportControlCallback;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700313 }
314
Jim Miller20daffd2013-10-07 14:59:53 -0700315 private void setEnableMarquee(boolean enabled) {
316 if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
Adam Powell31049d72013-10-07 12:58:42 -0700317 if (mTrackTitle != null) mTrackTitle.setSelected(enabled);
318 if (mTrackArtistAlbum != null) mTrackTitle.setSelected(enabled);
Jim Miller20daffd2013-10-07 14:59:53 -0700319 }
320
Jim Millerff2aa0b2012-09-06 19:03:52 -0700321 @Override
322 public void onFinishInflate() {
323 super.onFinishInflate();
Adam Powellf8895242013-09-30 16:16:24 -0700324 mInfoContainer = (ViewGroup) findViewById(R.id.info_container);
325 mMetadataContainer = (ViewGroup) findViewById(R.id.metadata_container);
326 mBadge = (ImageView) findViewById(R.id.badge);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700327 mTrackTitle = (TextView) findViewById(R.id.title);
Adam Powellf8895242013-09-30 16:16:24 -0700328 mTrackArtistAlbum = (TextView) findViewById(R.id.artist_album);
Adam Powellf8895242013-09-30 16:16:24 -0700329 mTransientSeek = findViewById(R.id.transient_seek);
330 mTransientSeekBar = (SeekBar) findViewById(R.id.transient_seek_bar);
331 mTransientSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
332 mTransientSeekTimeElapsed = (TextView) findViewById(R.id.transient_seek_time_elapsed);
Chris Wren6af85cc2013-10-29 13:19:54 -0400333 mTransientSeekTimeTotal = (TextView) findViewById(R.id.transient_seek_time_remaining);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700334 mBtnPrev = (ImageView) findViewById(R.id.btn_prev);
335 mBtnPlay = (ImageView) findViewById(R.id.btn_play);
336 mBtnNext = (ImageView) findViewById(R.id.btn_next);
337 final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext };
338 for (View view : buttons) {
Adam Powellf8895242013-09-30 16:16:24 -0700339 view.setOnClickListener(mTransportCommandListener);
340 view.setOnLongClickListener(mTransportShowSeekBarListener);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700341 }
Jim Miller20daffd2013-10-07 14:59:53 -0700342 final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
343 setEnableMarquee(screenOn);
Jim Millercc747ad2013-10-24 19:53:09 -0700344 // Allow long-press anywhere else in this view to show the seek bar
345 setOnLongClickListener(mTransportShowSeekBarListener);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700346 }
347
348 @Override
349 public void onAttachedToWindow() {
350 super.onAttachedToWindow();
351 if (DEBUG) Log.v(TAG, "onAttachToWindow()");
352 if (mPopulateMetadataWhenAttached != null) {
353 updateMetadata(mPopulateMetadataWhenAttached);
354 mPopulateMetadataWhenAttached = null;
355 }
Adam Powellf8895242013-09-30 16:16:24 -0700356 if (DEBUG) Log.v(TAG, "Registering TCV " + this);
Adam Powell3b435fb2013-10-15 19:15:39 -0700357 mMetadata.clear();
Adam Powellf8895242013-09-30 16:16:24 -0700358 mAudioManager.registerRemoteController(mRemoteController);
Jim Miller20daffd2013-10-07 14:59:53 -0700359 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitor);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700360 }
361
362 @Override
Adam Powellf8895242013-09-30 16:16:24 -0700363 protected void onConfigurationChanged(Configuration newConfig) {
364 super.onConfigurationChanged(newConfig);
365 final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
366 final int dim = Math.max(dm.widthPixels, dm.heightPixels);
367 mRemoteController.setArtworkConfiguration(true, dim, dim);
Jean-Michel Trivi9e589b92013-03-08 14:30:10 -0800368 }
369
370 @Override
Jim Millerff2aa0b2012-09-06 19:03:52 -0700371 public void onDetachedFromWindow() {
372 if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
373 super.onDetachedFromWindow();
Adam Powellf8895242013-09-30 16:16:24 -0700374 if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
375 mAudioManager.unregisterRemoteController(mRemoteController);
Jim Miller20daffd2013-10-07 14:59:53 -0700376 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
Adam Powell3b435fb2013-10-15 19:15:39 -0700377 mMetadata.clear();
Jim Millerb2c64692013-10-15 20:09:07 -0700378 removeCallbacks(mUpdateSeekBars);
Adam Powellf8895242013-09-30 16:16:24 -0700379 }
380
Craig Mautner83341922013-10-30 10:29:38 -0700381 @Override
382 protected Parcelable onSaveInstanceState() {
383 SavedState ss = new SavedState(super.onSaveInstanceState());
384 ss.artist = mMetadata.artist;
385 ss.trackTitle = mMetadata.trackTitle;
386 ss.albumTitle = mMetadata.albumTitle;
387 ss.duration = mMetadata.duration;
388 ss.bitmap = mMetadata.bitmap;
389 return ss;
390 }
391
392 @Override
393 protected void onRestoreInstanceState(Parcelable state) {
394 if (!(state instanceof SavedState)) {
395 super.onRestoreInstanceState(state);
396 return;
397 }
398 SavedState ss = (SavedState) state;
399 super.onRestoreInstanceState(ss.getSuperState());
400 mMetadata.artist = ss.artist;
401 mMetadata.trackTitle = ss.trackTitle;
402 mMetadata.albumTitle = ss.albumTitle;
403 mMetadata.duration = ss.duration;
404 mMetadata.bitmap = ss.bitmap;
405 populateMetadata();
406 }
407
Adam Powellf8895242013-09-30 16:16:24 -0700408 void setBadgeIcon(Drawable bmp) {
409 mBadge.setImageDrawable(bmp);
410
411 final ColorMatrix cm = new ColorMatrix();
412 cm.setSaturation(0);
413 mBadge.setColorFilter(new ColorMatrixColorFilter(cm));
Adam Powell31049d72013-10-07 12:58:42 -0700414 mBadge.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
Adam Powellf8895242013-09-30 16:16:24 -0700415 mBadge.setImageAlpha(0xef);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700416 }
417
Jim Millerff2aa0b2012-09-06 19:03:52 -0700418 class Metadata {
419 private String artist;
420 private String trackTitle;
421 private String albumTitle;
422 private Bitmap bitmap;
Adam Powellf8895242013-09-30 16:16:24 -0700423 private long duration;
424
425 public void clear() {
426 artist = null;
427 trackTitle = null;
428 albumTitle = null;
429 bitmap = null;
430 duration = -1;
431 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700432
433 public String toString() {
Adam Powellf8895242013-09-30 16:16:24 -0700434 return "Metadata[artist=" + artist + " trackTitle=" + trackTitle +
435 " albumTitle=" + albumTitle + " duration=" + duration + "]";
Jim Millerff2aa0b2012-09-06 19:03:52 -0700436 }
437 }
438
Adam Powellf8895242013-09-30 16:16:24 -0700439 void clearMetadata() {
440 mPopulateMetadataWhenAttached = null;
441 mMetadata.clear();
442 populateMetadata();
Jim Millerff2aa0b2012-09-06 19:03:52 -0700443 }
444
Adam Powellf8895242013-09-30 16:16:24 -0700445 void updateMetadata(RemoteController.MetadataEditor data) {
446 if (isAttachedToWindow()) {
447 mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
448 mMetadata.artist);
449 mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE,
450 mMetadata.trackTitle);
451 mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM,
452 mMetadata.albumTitle);
453 mMetadata.duration = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, -1);
454 mMetadata.bitmap = data.getBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
455 mMetadata.bitmap);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700456 populateMetadata();
457 } else {
458 mPopulateMetadataWhenAttached = data;
459 }
460 }
461
462 /**
463 * Populates the given metadata into the view
464 */
465 private void populateMetadata() {
Adam Powellf8895242013-09-30 16:16:24 -0700466 if (ANIMATE_TRANSITIONS && isLaidOut() && mMetadataContainer.getVisibility() == VISIBLE) {
467 TransitionManager.beginDelayedTransition(mMetadataContainer, mMetadataChangeTransition);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700468 }
Adam Powellf8895242013-09-30 16:16:24 -0700469
470 final String remoteClientPackage = mRemoteController.getRemoteControlClientPackageName();
471 Drawable badgeIcon = null;
472 try {
473 badgeIcon = getContext().getPackageManager().getApplicationIcon(remoteClientPackage);
474 } catch (PackageManager.NameNotFoundException e) {
475 Log.e(TAG, "Couldn't get remote control client package icon", e);
476 }
477 setBadgeIcon(badgeIcon);
Adam Powell3b435fb2013-10-15 19:15:39 -0700478 mTrackTitle.setText(!TextUtils.isEmpty(mMetadata.trackTitle)
479 ? mMetadata.trackTitle : null);
480
481 final StringBuilder sb = new StringBuilder();
Jim Millerff2aa0b2012-09-06 19:03:52 -0700482 if (!TextUtils.isEmpty(mMetadata.artist)) {
483 if (sb.length() != 0) {
484 sb.append(" - ");
485 }
486 sb.append(mMetadata.artist);
487 }
488 if (!TextUtils.isEmpty(mMetadata.albumTitle)) {
489 if (sb.length() != 0) {
490 sb.append(" - ");
491 }
492 sb.append(mMetadata.albumTitle);
493 }
Adam Powell3b435fb2013-10-15 19:15:39 -0700494
495 final String trackArtistAlbum = sb.toString();
496 mTrackArtistAlbum.setText(!TextUtils.isEmpty(trackArtistAlbum) ?
497 trackArtistAlbum : null);
Adam Powellf8895242013-09-30 16:16:24 -0700498
499 if (mMetadata.duration >= 0) {
500 setSeekBarsEnabled(true);
501 setSeekBarDuration(mMetadata.duration);
502
503 final String skeleton;
504
505 if (mMetadata.duration >= 86400000) {
506 skeleton = "DDD kk mm ss";
507 } else if (mMetadata.duration >= 3600000) {
508 skeleton = "kk mm ss";
509 } else {
510 skeleton = "mm ss";
511 }
512 mFormat = new SimpleDateFormat(DateFormat.getBestDateTimePattern(
513 getContext().getResources().getConfiguration().locale,
514 skeleton));
515 mFormat.setTimeZone(TimeZone.getTimeZone("GMT+0"));
516 } else {
517 setSeekBarsEnabled(false);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700518 }
519
Jim Miller1693f6a2013-10-20 17:21:57 -0700520 KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(mMetadata.bitmap);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700521 final int flags = mTransportControlFlags;
522 setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
523 setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
524 setVisibilityBasedOnFlag(mBtnPlay, flags,
525 RemoteControlClient.FLAG_KEY_MEDIA_PLAY
526 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
527 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
528 | RemoteControlClient.FLAG_KEY_MEDIA_STOP);
529
530 updatePlayPauseState(mCurrentPlayState);
531 }
532
Adam Powellf8895242013-09-30 16:16:24 -0700533 void updateSeekDisplay() {
534 if (mMetadata != null && mRemoteController != null && mFormat != null) {
Jim Millercc747ad2013-10-24 19:53:09 -0700535 mTempDate.setTime(mRemoteController.getEstimatedMediaPosition());
536 mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
537 mTempDate.setTime(mMetadata.duration);
538 mTransientSeekTimeTotal.setText(mFormat.format(mTempDate));
Adam Powellf8895242013-09-30 16:16:24 -0700539
Jim Millercc747ad2013-10-24 19:53:09 -0700540 if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate +
Chris Wren6af85cc2013-10-29 13:19:54 -0400541 " duration=" + mMetadata.duration);
Adam Powellf8895242013-09-30 16:16:24 -0700542 }
543 }
544
545 boolean tryToggleSeekBar() {
546 if (ANIMATE_TRANSITIONS) {
547 TransitionManager.beginDelayedTransition(mInfoContainer);
548 }
549 if (mTransientSeek.getVisibility() == VISIBLE) {
550 mTransientSeek.setVisibility(INVISIBLE);
551 mMetadataContainer.setVisibility(VISIBLE);
552 cancelResetToMetadata();
Jim Millercc747ad2013-10-24 19:53:09 -0700553 removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible
Adam Powellf8895242013-09-30 16:16:24 -0700554 } else {
555 mTransientSeek.setVisibility(VISIBLE);
556 mMetadataContainer.setVisibility(INVISIBLE);
557 delayResetToMetadata();
Jim Millercc747ad2013-10-24 19:53:09 -0700558 if (playbackPositionShouldMove(mCurrentPlayState)) {
559 mUpdateSeekBars.run();
560 } else {
561 mUpdateSeekBars.updateOnce();
562 }
Adam Powellf8895242013-09-30 16:16:24 -0700563 }
564 mTransportControlCallback.userActivity();
565 return true;
566 }
567
568 void resetToMetadata() {
569 if (ANIMATE_TRANSITIONS) {
570 TransitionManager.beginDelayedTransition(mInfoContainer);
571 }
572 if (mTransientSeek.getVisibility() == VISIBLE) {
573 mTransientSeek.setVisibility(INVISIBLE);
574 mMetadataContainer.setVisibility(VISIBLE);
575 }
576 // TODO Also hide ratings, if applicable
577 }
578
579 void delayResetToMetadata() {
580 removeCallbacks(mResetToMetadata);
581 postDelayed(mResetToMetadata, RESET_TO_METADATA_DELAY);
582 }
583
584 void cancelResetToMetadata() {
585 removeCallbacks(mResetToMetadata);
586 }
587
588 void setSeekBarDuration(long duration) {
589 mTransientSeekBar.setMax((int) duration);
590 }
591
592 void scrubTo(int progress) {
593 mRemoteController.seekTo(progress);
594 mTransportControlCallback.userActivity();
595 }
596
Jim Millerff2aa0b2012-09-06 19:03:52 -0700597 private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
598 if ((flags & flag) != 0) {
599 view.setVisibility(View.VISIBLE);
600 } else {
Adam Powellf5176922013-10-13 10:39:04 -0700601 view.setVisibility(View.INVISIBLE);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700602 }
603 }
604
605 private void updatePlayPauseState(int state) {
606 if (DEBUG) Log.v(TAG,
607 "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state);
608 if (state == mCurrentPlayState) {
609 return;
610 }
611 final int imageResId;
612 final int imageDescId;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700613 switch (state) {
614 case RemoteControlClient.PLAYSTATE_ERROR:
Jim Miller5ecd8112013-01-09 18:50:26 -0800615 imageResId = R.drawable.stat_sys_warning;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700616 // TODO use more specific image description string for warning, but here the "play"
617 // message is still valid because this button triggers a play command.
Jim Miller5ecd8112013-01-09 18:50:26 -0800618 imageDescId = R.string.keyguard_transport_play_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700619 break;
620
621 case RemoteControlClient.PLAYSTATE_PLAYING:
Jim Miller5ecd8112013-01-09 18:50:26 -0800622 imageResId = R.drawable.ic_media_pause;
623 imageDescId = R.string.keyguard_transport_pause_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700624 break;
625
626 case RemoteControlClient.PLAYSTATE_BUFFERING:
Jim Miller5ecd8112013-01-09 18:50:26 -0800627 imageResId = R.drawable.ic_media_stop;
628 imageDescId = R.string.keyguard_transport_stop_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700629 break;
630
631 case RemoteControlClient.PLAYSTATE_PAUSED:
632 default:
Jim Miller5ecd8112013-01-09 18:50:26 -0800633 imageResId = R.drawable.ic_media_play;
634 imageDescId = R.string.keyguard_transport_play_description;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700635 break;
636 }
Adam Powellf8895242013-09-30 16:16:24 -0700637
Jim Millercc747ad2013-10-24 19:53:09 -0700638 boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0;
639 setSeekBarsEnabled(clientSupportsSeek);
640
Jim Millerff2aa0b2012-09-06 19:03:52 -0700641 mBtnPlay.setImageResource(imageResId);
642 mBtnPlay.setContentDescription(getResources().getString(imageDescId));
Jim Millerff2aa0b2012-09-06 19:03:52 -0700643 mCurrentPlayState = state;
644 }
645
Adam Powellf8895242013-09-30 16:16:24 -0700646 boolean updateSeekBars() {
647 final int position = (int) mRemoteController.getEstimatedMediaPosition();
Jim Millercc747ad2013-10-24 19:53:09 -0700648 if (DEBUG) Log.v(TAG, "Estimated time:" + position);
Adam Powellf8895242013-09-30 16:16:24 -0700649 if (position >= 0) {
Jim Millercc747ad2013-10-24 19:53:09 -0700650 mTransientSeekBar.setProgress(position);
Adam Powellf8895242013-09-30 16:16:24 -0700651 return true;
652 }
653 Log.w(TAG, "Updating seek bars; received invalid estimated media position (" +
654 position + "). Disabling seek.");
655 setSeekBarsEnabled(false);
656 return false;
657 }
658
Jim Millerff2aa0b2012-09-06 19:03:52 -0700659 static class SavedState extends BaseSavedState {
Jim Miller223ce5c2012-10-05 19:13:23 -0700660 boolean clientPresent;
Craig Mautner83341922013-10-30 10:29:38 -0700661 String artist;
662 String trackTitle;
663 String albumTitle;
664 long duration;
665 Bitmap bitmap;
Jim Millerff2aa0b2012-09-06 19:03:52 -0700666
667 SavedState(Parcelable superState) {
668 super(superState);
669 }
670
671 private SavedState(Parcel in) {
672 super(in);
Craig Mautner83341922013-10-30 10:29:38 -0700673 clientPresent = in.readInt() != 0;
674 artist = in.readString();
675 trackTitle = in.readString();
676 albumTitle = in.readString();
677 duration = in.readLong();
678 bitmap = Bitmap.CREATOR.createFromParcel(in);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700679 }
680
681 @Override
682 public void writeToParcel(Parcel out, int flags) {
683 super.writeToParcel(out, flags);
Craig Mautner83341922013-10-30 10:29:38 -0700684 out.writeInt(clientPresent ? 1 : 0);
685 out.writeString(artist);
686 out.writeString(trackTitle);
687 out.writeString(albumTitle);
688 out.writeLong(duration);
689 bitmap.writeToParcel(out, flags);
Jim Millerff2aa0b2012-09-06 19:03:52 -0700690 }
691
692 public static final Parcelable.Creator<SavedState> CREATOR
693 = new Parcelable.Creator<SavedState>() {
694 public SavedState createFromParcel(Parcel in) {
695 return new SavedState(in);
696 }
697
698 public SavedState[] newArray(int size) {
699 return new SavedState[size];
700 }
701 };
702 }
703
Jim Millerff2aa0b2012-09-06 19:03:52 -0700704 private void sendMediaButtonClick(int keyCode) {
Adam Powellf8895242013-09-30 16:16:24 -0700705 // TODO We should think about sending these up/down events accurately with touch up/down
706 // on the buttons, but in the near term this will interfere with the long press behavior.
707 mRemoteController.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
708 mRemoteController.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
Jim Millerff2aa0b2012-09-06 19:03:52 -0700709
Adam Powellf8895242013-09-30 16:16:24 -0700710 mTransportControlCallback.userActivity();
Jim Millerff2aa0b2012-09-06 19:03:52 -0700711 }
712
713 public boolean providesClock() {
714 return false;
715 }
Jim Millerff2aa0b2012-09-06 19:03:52 -0700716}