RemoteControlClient.java revision f823fc4dba2df5cf5f00e13361f2db93c81f6961
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.app.PendingIntent;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.RectF;
27import android.media.MediaMetadataRetriever;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Looper;
32import android.os.Message;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemClock;
36import android.util.Log;
37
38import java.lang.IllegalArgumentException;
39import java.util.ArrayList;
40import java.util.Iterator;
41
42/**
43 * RemoteControlClient enables exposing information meant to be consumed by remote controls
44 * capable of displaying metadata, artwork and media transport control buttons.
45 *
46 * <p>A remote control client object is associated with a media button event receiver. This
47 * event receiver must have been previously registered with
48 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
49 * RemoteControlClient can be registered through
50 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
51 *
52 * <p>Here is an example of creating a RemoteControlClient instance after registering a media
53 * button event receiver:
54 * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
55 * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
56 * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
57 * // build the PendingIntent for the remote control client
58 * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
59 * mediaButtonIntent.setComponent(myEventReceiver);
60 * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
61 * // create and register the remote control client
62 * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
63 * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre>
64 */
65public class RemoteControlClient
66{
67    private final static String TAG = "RemoteControlClient";
68    private final static boolean DEBUG = false;
69
70    /**
71     * Playback state of a RemoteControlClient which is stopped.
72     *
73     * @see #setPlaybackState(int)
74     */
75    public final static int PLAYSTATE_STOPPED            = 1;
76    /**
77     * Playback state of a RemoteControlClient which is paused.
78     *
79     * @see #setPlaybackState(int)
80     */
81    public final static int PLAYSTATE_PAUSED             = 2;
82    /**
83     * Playback state of a RemoteControlClient which is playing media.
84     *
85     * @see #setPlaybackState(int)
86     */
87    public final static int PLAYSTATE_PLAYING            = 3;
88    /**
89     * Playback state of a RemoteControlClient which is fast forwarding in the media
90     *    it is currently playing.
91     *
92     * @see #setPlaybackState(int)
93     */
94    public final static int PLAYSTATE_FAST_FORWARDING    = 4;
95    /**
96     * Playback state of a RemoteControlClient which is fast rewinding in the media
97     *    it is currently playing.
98     *
99     * @see #setPlaybackState(int)
100     */
101    public final static int PLAYSTATE_REWINDING          = 5;
102    /**
103     * Playback state of a RemoteControlClient which is skipping to the next
104     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
105     *
106     * @see #setPlaybackState(int)
107     */
108    public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
109    /**
110     * Playback state of a RemoteControlClient which is skipping back to the previous
111     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
112     *
113     * @see #setPlaybackState(int)
114     */
115    public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
116    /**
117     * Playback state of a RemoteControlClient which is buffering data to play before it can
118     *    start or resume playback.
119     *
120     * @see #setPlaybackState(int)
121     */
122    public final static int PLAYSTATE_BUFFERING          = 8;
123    /**
124     * Playback state of a RemoteControlClient which cannot perform any playback related
125     *    operation because of an internal error. Examples of such situations are no network
126     *    connectivity when attempting to stream data from a server, or expired user credentials
127     *    when trying to play subscription-based content.
128     *
129     * @see #setPlaybackState(int)
130     */
131    public final static int PLAYSTATE_ERROR              = 9;
132    /**
133     * @hide
134     * The value of a playback state when none has been declared.
135     * Intentionally hidden as an application shouldn't set such a playback state value.
136     */
137    public final static int PLAYSTATE_NONE               = 0;
138
139    /**
140     * @hide
141     * The default playback type, "local", indicating the presentation of the media is happening on
142     * the same device (e.g. a phone, a tablet) as where it is controlled from.
143     */
144    public final static int PLAYBACK_TYPE_LOCAL = 0;
145    /**
146     * @hide
147     * A playback type indicating the presentation of the media is happening on
148     * a different device (i.e. the remote device) than where it is controlled from.
149     */
150    public final static int PLAYBACK_TYPE_REMOTE = 1;
151    private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
152    private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
153    /**
154     * @hide
155     * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
156     * from this object. An example of fixed playback volume is a remote player, playing over HDMI
157     * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
158     * source.
159     * @see #PLAYBACKINFO_VOLUME_HANDLING.
160     */
161    public final static int PLAYBACK_VOLUME_FIXED = 0;
162    /**
163     * @hide
164     * Playback information indicating the playback volume is variable and can be controlled from
165     * this object.
166     * @see #PLAYBACKINFO_VOLUME_HANDLING.
167     */
168    public final static int PLAYBACK_VOLUME_VARIABLE = 1;
169    /**
170     * @hide (to be un-hidden)
171     * The playback information value indicating the value of a given information type is invalid.
172     * @see #PLAYBACKINFO_VOLUME_HANDLING.
173     */
174    public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
175
176    /**
177     * @hide
178     * An unknown or invalid playback position value.
179     */
180    public final static long PLAYBACK_POSITION_INVALID = -1;
181    /**
182     * @hide
183     * An invalid playback position value associated with the use of {@link #setPlaybackState(int)}
184     * used to indicate that playback position will remain unknown.
185     */
186    public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L;
187    /**
188     * @hide
189     * The default playback speed, 1x.
190     */
191    public final static float PLAYBACK_SPEED_1X = 1.0f;
192
193    //==========================================
194    // Public keys for playback information
195    /**
196     * @hide
197     * Playback information that defines the type of playback associated with this
198     * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
199     */
200    public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
201    /**
202     * @hide
203     * Playback information that defines at what volume the playback associated with this
204     * RemoteControlClient is performed. This information is only used when the playback type is not
205     * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
206     */
207    public final static int PLAYBACKINFO_VOLUME = 2;
208    /**
209     * @hide
210     * Playback information that defines the maximum volume volume value that is supported
211     * by the playback associated with this RemoteControlClient. This information is only used
212     * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
213     */
214    public final static int PLAYBACKINFO_VOLUME_MAX = 3;
215    /**
216     * @hide
217     * Playback information that defines how volume is handled for the presentation of the media.
218     * @see #PLAYBACK_VOLUME_FIXED
219     * @see #PLAYBACK_VOLUME_VARIABLE
220     */
221    public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
222    /**
223     * @hide
224     * Playback information that defines over what stream type the media is presented.
225     */
226    public final static int PLAYBACKINFO_USES_STREAM = 5;
227
228    //==========================================
229    // Public flags for the supported transport control capabilities
230    /**
231     * Flag indicating a RemoteControlClient makes use of the "previous" media key.
232     *
233     * @see #setTransportControlFlags(int)
234     * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
235     */
236    public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
237    /**
238     * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
239     *
240     * @see #setTransportControlFlags(int)
241     * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
242     */
243    public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
244    /**
245     * Flag indicating a RemoteControlClient makes use of the "play" media key.
246     *
247     * @see #setTransportControlFlags(int)
248     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
249     */
250    public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
251    /**
252     * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
253     *
254     * @see #setTransportControlFlags(int)
255     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
256     */
257    public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
258    /**
259     * Flag indicating a RemoteControlClient makes use of the "pause" media key.
260     *
261     * @see #setTransportControlFlags(int)
262     * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
263     */
264    public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
265    /**
266     * Flag indicating a RemoteControlClient makes use of the "stop" media key.
267     *
268     * @see #setTransportControlFlags(int)
269     * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
270     */
271    public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
272    /**
273     * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
274     *
275     * @see #setTransportControlFlags(int)
276     * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
277     */
278    public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
279    /**
280     * Flag indicating a RemoteControlClient makes use of the "next" media key.
281     *
282     * @see #setTransportControlFlags(int)
283     * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
284     */
285    public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
286    /**
287     * Flag indicating a RemoteControlClient can receive changes in the media playback position
288     * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set
289     * in order for components that display the RemoteControlClient information, to display and
290     * let the user control media playback position.
291     * @see #setTransportControlFlags(int)
292     * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener)
293     * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener)
294     */
295    public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
296    /**
297     * @hide
298     * CANDIDATE FOR PUBLIC API
299     * Flag indicating a RemoteControlClient supports ratings.
300     * This flag must be set in order for components that display the RemoteControlClient
301     * information, to display ratings information, and, if ratings are declared editable
302     * (by calling {@link MetadataEditor#addEditableKey(int)} with the
303     * {@link MetadataEditor#LONG_KEY_RATING_BY_USER} key), it will enable the user to rate
304     * the media.
305     * @see #setTransportControlFlags(int)
306     */
307    public final static int FLAG_KEY_MEDIA_RATING = 1 << 9;
308
309    /**
310     * @hide
311     * The flags for when no media keys are declared supported.
312     * Intentionally hidden as an application shouldn't set the transport control flags
313     *     to this value.
314     */
315    public final static int FLAGS_KEY_MEDIA_NONE = 0;
316
317    /**
318     * @hide
319     * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
320     */
321    public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
322    /**
323     * @hide
324     * Flag used to signal that the transport control buttons supported by the
325     *     RemoteControlClient are requested.
326     * This can for instance happen when playback is at the end of a playlist, and the "next"
327     * operation is not supported anymore.
328     */
329    public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
330    /**
331     * @hide
332     * Flag used to signal that the playback state of the RemoteControlClient is requested.
333     */
334    public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
335    /**
336     * @hide
337     * Flag used to signal that the album art for the RemoteControlClient is requested.
338     */
339    public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
340
341    /**
342     * Class constructor.
343     * @param mediaButtonIntent The intent that will be sent for the media button events sent
344     *     by remote controls.
345     *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
346     *     action, and have a component that will handle the intent (set with
347     *     {@link Intent#setComponent(ComponentName)}) registered with
348     *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
349     *     before this new RemoteControlClient can itself be registered with
350     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
351     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
352     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
353     */
354    public RemoteControlClient(PendingIntent mediaButtonIntent) {
355        mRcMediaIntent = mediaButtonIntent;
356
357        Looper looper;
358        if ((looper = Looper.myLooper()) != null) {
359            mEventHandler = new EventHandler(this, looper);
360        } else if ((looper = Looper.getMainLooper()) != null) {
361            mEventHandler = new EventHandler(this, looper);
362        } else {
363            mEventHandler = null;
364            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
365        }
366    }
367
368    /**
369     * Class constructor for a remote control client whose internal event handling
370     * happens on a user-provided Looper.
371     * @param mediaButtonIntent The intent that will be sent for the media button events sent
372     *     by remote controls.
373     *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
374     *     action, and have a component that will handle the intent (set with
375     *     {@link Intent#setComponent(ComponentName)}) registered with
376     *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
377     *     before this new RemoteControlClient can itself be registered with
378     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
379     * @param looper The Looper running the event loop.
380     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
381     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
382     */
383    public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
384        mRcMediaIntent = mediaButtonIntent;
385
386        mEventHandler = new EventHandler(this, looper);
387    }
388
389    private static final int[] METADATA_KEYS_TYPE_STRING = {
390        MediaMetadataRetriever.METADATA_KEY_ALBUM,
391        MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
392        MediaMetadataRetriever.METADATA_KEY_TITLE,
393        MediaMetadataRetriever.METADATA_KEY_ARTIST,
394        MediaMetadataRetriever.METADATA_KEY_AUTHOR,
395        MediaMetadataRetriever.METADATA_KEY_COMPILATION,
396        MediaMetadataRetriever.METADATA_KEY_COMPOSER,
397        MediaMetadataRetriever.METADATA_KEY_DATE,
398        MediaMetadataRetriever.METADATA_KEY_GENRE,
399        MediaMetadataRetriever.METADATA_KEY_TITLE,
400        MediaMetadataRetriever.METADATA_KEY_WRITER };
401    private static final int[] METADATA_KEYS_TYPE_LONG = {
402        MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
403        MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
404        MediaMetadataRetriever.METADATA_KEY_DURATION,
405        MetadataEditor.LONG_KEY_RATING_TYPE,
406        MetadataEditor.LONG_KEY_RATING_BY_OTHERS,
407        MetadataEditor.LONG_KEY_RATING_BY_USER};
408
409    /**
410     * Class used to modify metadata in a {@link RemoteControlClient} object.
411     * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
412     * on which you set the metadata for the RemoteControlClient instance. Once all the information
413     * has been set, use {@link #apply()} to make it the new metadata that should be displayed
414     * for the associated client. Once the metadata has been "applied", you cannot reuse this
415     * instance of the MetadataEditor.
416     */
417    public class MetadataEditor {
418        /**
419         * Mask of editable keys.
420         */
421        private long mEditableKeys;
422        /**
423         * @hide
424         */
425        protected boolean mMetadataChanged;
426        /**
427         * @hide
428         */
429        protected boolean mArtworkChanged;
430        /**
431         * @hide
432         */
433        protected Bitmap mEditorArtwork;
434        /**
435         * @hide
436         */
437        protected Bundle mEditorMetadata;
438        private boolean mApplied = false;
439
440        // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
441        private MetadataEditor() { }
442        /**
443         * @hide
444         */
445        public Object clone() throws CloneNotSupportedException {
446            throw new CloneNotSupportedException();
447        }
448
449        /**
450         * The metadata key for the content artwork / album art.
451         */
452        public final static int BITMAP_KEY_ARTWORK = 100;
453        /**
454         * @hide
455         * CANDIDATE FOR PUBLIC API
456         * The metadata key qualifying the content rating.
457         * The value associated with this key may be: {@link #RATING_HEART},
458         * {@link #RATING_THUMB_UP_DOWN}, or a non-null positive integer expressing a maximum
459         * number of "stars" for the rating, for which a typical value is 3 or 5.
460         */
461        public final static int LONG_KEY_RATING_TYPE = 101;
462        /**
463         * @hide
464         * CANDIDATE FOR PUBLIC API
465         * The metadata key for the content's average rating, not the user's rating.
466         * The value associated with this key may be: an integer value between 0 and 100,
467         * or {@link #RATING_NOT_RATED} to express that no average rating is available.
468         * <p></p>
469         * Note that a rating value up to 100 is not incompatible with a rating type using up
470         * to 5 stars for instance, as the average may be an non-integer number of stars.
471         * <p></p>
472         * When the rating type is:
473         * <ul>
474         * <li>{@link #RATING_HEART}, a rating of 50 to 100 means "heart selected",</li>
475         * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 49 means "thumb down", 50 means
476         *     both "thumb up" and "thumb down" selected, 51 to 100 means "thumb up"</li>
477         * <li>a non-null positive integer, the rating value is mapped to the number of stars, e.g.
478         *     with a maximum of 5 stars, a rating of 21 to 40 maps to 2 stars.</li>
479         * </ul>
480         * @see #LONG_KEY_RATING_BY_USER
481         */
482        public final static int LONG_KEY_RATING_BY_OTHERS = 102;
483
484        // editable keys
485        /**
486         * @hide
487         * Editable key mask
488         */
489        public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF;
490        /**
491         * @hide
492         * CANDIDATE FOR PUBLIC API
493         * The metadata key for the content's rating by the user.
494         * The value associated with this key may be: an integer value between 0 and 100,
495         * or {@link #RATING_NOT_RATED} to express that the user hasn't rated this content.
496         * Rules for the interpretation of the rating value according to the rating style are
497         * the same as for {@link #LONG_KEY_RATING_BY_OTHERS}
498         */
499        public final static int LONG_KEY_RATING_BY_USER = 0x10000001;
500
501        /**
502         * @hide
503         * CANDIDATE FOR PUBLIC API
504         * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
505         * indicate the content referred to is a favorite (or not).
506         * @see #LONG_KEY_RATING_TYPE
507         */
508        public final static long RATING_HEART = -1;
509        /**
510         * @hide
511         * CANDIDATE FOR PUBLIC API
512         * A rating style for "thumb up" vs "thumb down".
513         * @see #LONG_KEY_RATING_TYPE
514         */
515        public final static long RATING_THUMB_UP_DOWN = -2;
516        /**
517         * @hide
518         * CANDIDATE FOR PUBLIC API
519         * A rating value indicating no rating is available.
520         * @see #LONG_KEY_RATING_BY_OTHERS
521         * @see #LONG_KEY_RATING_BY_USER
522         */
523        public final static long RATING_NOT_RATED = -101;
524
525        /**
526         * @hide
527         * TODO(jmtrivi) have lockscreen and music move to the new key name
528         */
529        public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
530
531        /**
532         * Adds textual information to be displayed.
533         * Note that none of the information added after {@link #apply()} has been called,
534         * will be displayed.
535         * @param key The identifier of a the metadata field to set. Valid values are
536         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
537         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
538         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
539         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
540         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
541         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
542         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
543         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
544         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
545         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
546         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
547         * @param value The text for the given key, or {@code null} to signify there is no valid
548         *      information for the field.
549         * @return Returns a reference to the same MetadataEditor object, so you can chain put
550         *      calls together.
551         */
552        public synchronized MetadataEditor putString(int key, String value)
553                throws IllegalArgumentException {
554            if (mApplied) {
555                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
556                return this;
557            }
558            if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
559                throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
560            }
561            mEditorMetadata.putString(String.valueOf(key), value);
562            mMetadataChanged = true;
563            return this;
564        }
565
566        /**
567         * Adds numerical information to be displayed.
568         * Note that none of the information added after {@link #apply()} has been called,
569         * will be displayed.
570         * @param key the identifier of a the metadata field to set. Valid values are
571         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
572         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
573         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
574         *      expressed in milliseconds),
575         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
576         * @param value The long value for the given key
577         * @return Returns a reference to the same MetadataEditor object, so you can chain put
578         *      calls together.
579         * @throws IllegalArgumentException
580         */
581        public synchronized MetadataEditor putLong(int key, long value)
582                throws IllegalArgumentException {
583            if (mApplied) {
584                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
585                return this;
586            }
587            if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
588                throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
589            }
590            mEditorMetadata.putLong(String.valueOf(key), value);
591            mMetadataChanged = true;
592            return this;
593        }
594
595        /**
596         * Sets the album / artwork picture to be displayed on the remote control.
597         * @param key the identifier of the bitmap to set. The only valid value is
598         *      {@link #BITMAP_KEY_ARTWORK}
599         * @param bitmap The bitmap for the artwork, or null if there isn't any.
600         * @return Returns a reference to the same MetadataEditor object, so you can chain put
601         *      calls together.
602         * @throws IllegalArgumentException
603         * @see android.graphics.Bitmap
604         */
605        public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
606                throws IllegalArgumentException {
607            if (mApplied) {
608                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
609                return this;
610            }
611            if (key != BITMAP_KEY_ARTWORK) {
612                throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
613            }
614            mEditorArtwork = bitmap;
615            mArtworkChanged = true;
616            return this;
617        }
618
619        /**
620         * Clears all the metadata that has been set since the MetadataEditor instance was
621         *     created with {@link RemoteControlClient#editMetadata(boolean)}.
622         */
623        // TODO add in javadoc that this doesn't call clearEditableKeys()
624        public synchronized void clear() {
625            if (mApplied) {
626                Log.e(TAG, "Can't clear a previously applied MetadataEditor");
627                return;
628            }
629            mEditorMetadata.clear();
630            mEditorArtwork = null;
631        }
632
633        /**
634         * @hide
635         * CANDIDATE FOR PUBLIC API
636         */
637        public synchronized void addEditableKey(int key) {
638            if (mApplied) {
639                Log.e(TAG, "Can't change editable keys of a previously applied MetadataEditor");
640                return;
641            }
642            // only one editable key at the moment, so we're not wasting memory on an array
643            // of editable keys to check the validity of the key, just hardcode the supported key.
644            if (key == MetadataEditor.LONG_KEY_RATING_BY_USER) {
645                mEditableKeys |= (MetadataEditor.KEY_EDITABLE_MASK & key);
646                mMetadataChanged = true;
647            } else {
648                Log.e(TAG, "Metadata key " + key + " cannot be edited");
649            }
650        }
651
652        /**
653         * @hide
654         * CANDIDATE FOR PUBLIC API
655         */
656        public synchronized void clearEditableKeys() {
657            if (mApplied) {
658                Log.e(TAG, "Can't clear editable keys of a previously applied MetadataEditor");
659                return;
660            }
661            if (mEditableKeys != 0) {
662                mEditableKeys = 0;
663                mMetadataChanged = true;
664            }
665        }
666
667        /**
668         * Associates all the metadata that has been set since the MetadataEditor instance was
669         *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
670         *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
671         *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
672         */
673        public synchronized void apply() {
674            if (mApplied) {
675                Log.e(TAG, "Can't apply a previously applied MetadataEditor");
676                return;
677            }
678            synchronized(mCacheLock) {
679                // assign the edited data
680                mMetadata = new Bundle(mEditorMetadata);
681                // add the information about editable keys
682                mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys);
683                if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
684                    mOriginalArtwork.recycle();
685                }
686                mOriginalArtwork = mEditorArtwork;
687                mEditorArtwork = null;
688                if (mMetadataChanged & mArtworkChanged) {
689                    // send to remote control display if conditions are met
690                    sendMetadataWithArtwork_syncCacheLock();
691                } else if (mMetadataChanged) {
692                    // send to remote control display if conditions are met
693                    sendMetadata_syncCacheLock();
694                } else if (mArtworkChanged) {
695                    // send to remote control display if conditions are met
696                    sendArtwork_syncCacheLock();
697                }
698                mApplied = true;
699            }
700        }
701    }
702
703    /**
704     * Creates a {@link MetadataEditor}.
705     * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
706     *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
707     * @return a new MetadataEditor instance.
708     */
709    public MetadataEditor editMetadata(boolean startEmpty) {
710        MetadataEditor editor = new MetadataEditor();
711        if (startEmpty) {
712            editor.mEditorMetadata = new Bundle();
713            editor.mEditorArtwork = null;
714            editor.mMetadataChanged = true;
715            editor.mArtworkChanged = true;
716            editor.mEditableKeys = 0;
717        } else {
718            editor.mEditorMetadata = new Bundle(mMetadata);
719            editor.mEditorArtwork = mOriginalArtwork;
720            editor.mMetadataChanged = false;
721            editor.mArtworkChanged = false;
722        }
723        return editor;
724    }
725
726    /**
727     * Sets the current playback state.
728     * @param state The current playback state, one of the following values:
729     *       {@link #PLAYSTATE_STOPPED},
730     *       {@link #PLAYSTATE_PAUSED},
731     *       {@link #PLAYSTATE_PLAYING},
732     *       {@link #PLAYSTATE_FAST_FORWARDING},
733     *       {@link #PLAYSTATE_REWINDING},
734     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
735     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
736     *       {@link #PLAYSTATE_BUFFERING},
737     *       {@link #PLAYSTATE_ERROR}.
738     */
739    public void setPlaybackState(int state) {
740        setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X,
741                false /* legacy API, converting to method with position and speed */);
742    }
743
744    /**
745     * Sets the current playback state and the matching media position for the current playback
746     *   speed.
747     * @param state The current playback state, one of the following values:
748     *       {@link #PLAYSTATE_STOPPED},
749     *       {@link #PLAYSTATE_PAUSED},
750     *       {@link #PLAYSTATE_PLAYING},
751     *       {@link #PLAYSTATE_FAST_FORWARDING},
752     *       {@link #PLAYSTATE_REWINDING},
753     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
754     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
755     *       {@link #PLAYSTATE_BUFFERING},
756     *       {@link #PLAYSTATE_ERROR}.
757     * @param timeInMs a 0 or positive value for the current media position expressed in ms
758     *    (same unit as for when sending the media duration, if applicable, with
759     *    {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the
760     *    {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not
761     *    known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state
762     *    is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
763     * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
764     *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
765     *    playing (e.g. when state is {@link #PLAYSTATE_ERROR}).
766     */
767    public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
768        setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
769    }
770
771    private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed,
772            boolean hasPosition) {
773        synchronized(mCacheLock) {
774            if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
775                    || (mPlaybackSpeed != playbackSpeed)) {
776                // store locally
777                mPlaybackState = state;
778                // distinguish between an application not knowing the current playback position
779                // at the moment and an application using the API where only the playback state
780                // is passed, not the playback position.
781                if (hasPosition) {
782                    if (timeInMs < 0) {
783                        mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
784                    } else {
785                        mPlaybackPositionMs = timeInMs;
786                    }
787                } else {
788                    mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN;
789                }
790                mPlaybackSpeed = playbackSpeed;
791                // keep track of when the state change occurred
792                mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
793
794                // send to remote control display if conditions are met
795                sendPlaybackState_syncCacheLock();
796                // update AudioService
797                sendAudioServiceNewPlaybackState_syncCacheLock();
798
799                // handle automatic playback position refreshes
800                initiateCheckForDrift_syncCacheLock();
801            }
802        }
803    }
804
805    private void initiateCheckForDrift_syncCacheLock() {
806        if (mEventHandler == null) {
807            return;
808        }
809        mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
810        if (!mNeedsPositionSync) {
811            return;
812        }
813        if (mPlaybackPositionMs < 0) {
814            // the current playback state has no known playback position, it's no use
815            // trying to see if there is any drift at this point
816            // (this also bypasses this mechanism for older apps that use the old
817            //  setPlaybackState(int) API)
818            return;
819        }
820        if (playbackPositionShouldMove(mPlaybackState)) {
821            // playback position moving, schedule next position drift check
822            mEventHandler.sendMessageDelayed(
823                    mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
824                    getCheckPeriodFromSpeed(mPlaybackSpeed));
825        }
826    }
827
828    private void onPositionDriftCheck() {
829        if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
830        synchronized(mCacheLock) {
831            if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
832                return;
833            }
834            if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
835                if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
836                return;
837            }
838            long estPos = mPlaybackPositionMs + (long)
839                    ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
840            long actPos = mPositionProvider.onGetPlaybackPosition();
841            if (actPos >= 0) {
842                if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
843                    // drift happened, report the new position
844                    if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +"  est=" +estPos); }
845                    setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
846                } else {
847                    if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +"  est=" + estPos); }
848                    // no drift, schedule the next drift check
849                    mEventHandler.sendMessageDelayed(
850                            mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
851                            getCheckPeriodFromSpeed(mPlaybackSpeed));
852                }
853            } else {
854                // invalid position (negative value), can't check for drift
855                mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
856            }
857        }
858    }
859
860    /**
861     * Sets the flags for the media transport control buttons that this client supports.
862     * @param transportControlFlags A combination of the following flags:
863     *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
864     *      {@link #FLAG_KEY_MEDIA_REWIND},
865     *      {@link #FLAG_KEY_MEDIA_PLAY},
866     *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
867     *      {@link #FLAG_KEY_MEDIA_PAUSE},
868     *      {@link #FLAG_KEY_MEDIA_STOP},
869     *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
870     *      {@link #FLAG_KEY_MEDIA_NEXT},
871     *      {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}
872     */
873    public void setTransportControlFlags(int transportControlFlags) {
874        synchronized(mCacheLock) {
875            // store locally
876            mTransportControlFlags = transportControlFlags;
877
878            // send to remote control display if conditions are met
879            sendTransportControlInfo_syncCacheLock();
880        }
881    }
882
883    /**
884     * @hide
885     * CANDIDATE FOR PUBLIC API
886     * TODO ADD DESCRIPTION
887     */
888    public interface OnMetadataUpdateListener  {
889        /**
890         * TODO ADD DESCRIPTION
891         * @param key
892         * @param newValue
893         */
894        void onMetadataUpdateLong(int key, long newValue);
895        /**
896         * TODO ADD DESCRIPTION
897         * @param key
898         * @param newValue
899         */
900        void onMetadataUpdateString(int key, String newValue);
901        /**
902         * TODO ADD DESCRIPTION
903         * @param key
904         * @param newValue
905         */
906        void onMetadataUpdateBitmap(int key, Bitmap newValue);
907    }
908
909    /**
910     * @hide
911     * CANDIDATE FOR PUBLIC API
912     * TODO ADD DESCRIPTION
913     * @param l
914     */
915    public void setMetadataUpdateListener(OnMetadataUpdateListener l) {
916        synchronized(mCacheLock) {
917            mMetadataUpdateListener = l;
918        }
919    }
920
921
922    /**
923     * Interface definition for a callback to be invoked when the media playback position is
924     * requested to be updated.
925     * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
926     */
927    public interface OnPlaybackPositionUpdateListener {
928        /**
929         * Called on the implementer to notify it that the playback head should be set at the given
930         * position. If the position can be changed from its current value, the implementor of
931         * the interface must also update the playback position using
932         * {@link #setPlaybackState(int, long, float)} to reflect the actual new
933         * position being used, regardless of whether it differs from the requested position.
934         * Failure to do so would cause the system to not know the new actual playback position,
935         * and user interface components would fail to show the user where playback resumed after
936         * the position was updated.
937         * @param newPositionMs the new requested position in the current media, expressed in ms.
938         */
939        void onPlaybackPositionUpdate(long newPositionMs);
940    }
941
942    /**
943     * Interface definition for a callback to be invoked when the media playback position is
944     * queried.
945     * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
946     */
947    public interface OnGetPlaybackPositionListener {
948        /**
949         * Called on the implementer of the interface to query the current playback position.
950         * @return a negative value if the current playback position (or the last valid playback
951         *     position) is not known, or a zero or positive value expressed in ms indicating the
952         *     current position, or the last valid known position.
953         */
954        long onGetPlaybackPosition();
955    }
956
957    /**
958     * Sets the listener to be called whenever the media playback position is requested
959     * to be updated.
960     * Notifications will be received in the same thread as the one in which RemoteControlClient
961     * was created.
962     * @param l the position update listener to be called
963     */
964    public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
965        synchronized(mCacheLock) {
966            int oldCapa = mPlaybackPositionCapabilities;
967            if (l != null) {
968                mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE;
969            } else {
970                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE;
971            }
972            mPositionUpdateListener = l;
973            if (oldCapa != mPlaybackPositionCapabilities) {
974                // tell RCDs that this RCC's playback position capabilities have changed
975                sendTransportControlInfo_syncCacheLock();
976            }
977        }
978    }
979
980    /**
981     * Sets the listener to be called whenever the media current playback position is needed.
982     * Queries will be received in the same thread as the one in which RemoteControlClient
983     * was created.
984     * @param l the listener to be called to retrieve the playback position
985     */
986    public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
987        synchronized(mCacheLock) {
988            int oldCapa = mPlaybackPositionCapabilities;
989            if (l != null) {
990                mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE;
991            } else {
992                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE;
993            }
994            mPositionProvider = l;
995            if (oldCapa != mPlaybackPositionCapabilities) {
996                // tell RCDs that this RCC's playback position capabilities have changed
997                sendTransportControlInfo_syncCacheLock();
998            }
999            if ((mPositionProvider != null) && (mEventHandler != null)
1000                    && playbackPositionShouldMove(mPlaybackState)) {
1001                // playback position is already moving, but now we have a position provider,
1002                // so schedule a drift check right now
1003                mEventHandler.sendMessageDelayed(
1004                        mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
1005                        0 /*check now*/);
1006            }
1007        }
1008    }
1009
1010    /**
1011     * @hide
1012     * Flag to reflect that the application controlling this RemoteControlClient sends playback
1013     * position updates. The playback position being "readable" is considered from the application's
1014     * point of view.
1015     */
1016    public static int MEDIA_POSITION_READABLE = 1 << 0;
1017    /**
1018     * @hide
1019     * Flag to reflect that the application controlling this RemoteControlClient can receive
1020     * playback position updates. The playback position being "writable"
1021     * is considered from the application's point of view.
1022     */
1023    public static int MEDIA_POSITION_WRITABLE = 1 << 1;
1024
1025    private int mPlaybackPositionCapabilities = 0;
1026
1027    /** @hide */
1028    public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
1029    /** @hide */
1030    // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
1031    public final static int DEFAULT_PLAYBACK_VOLUME = 15;
1032
1033    private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
1034    private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
1035    private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
1036    private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
1037    private int mPlaybackStream = AudioManager.STREAM_MUSIC;
1038
1039    /**
1040     * @hide
1041     * Set information describing information related to the playback of media so the system
1042     * can implement additional behavior to handle non-local playback usecases.
1043     * @param what a key to specify the type of information to set. Valid keys are
1044     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
1045     *        {@link #PLAYBACKINFO_USES_STREAM},
1046     *        {@link #PLAYBACKINFO_VOLUME},
1047     *        {@link #PLAYBACKINFO_VOLUME_MAX},
1048     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
1049     * @param value the value for the supplied information to set.
1050     */
1051    public void setPlaybackInformation(int what, int value) {
1052        synchronized(mCacheLock) {
1053            switch (what) {
1054                case PLAYBACKINFO_PLAYBACK_TYPE:
1055                    if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
1056                        if (mPlaybackType != value) {
1057                            mPlaybackType = value;
1058                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
1059                        }
1060                    } else {
1061                        Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
1062                    }
1063                    break;
1064                case PLAYBACKINFO_VOLUME:
1065                    if ((value > -1) && (value <= mPlaybackVolumeMax)) {
1066                        if (mPlaybackVolume != value) {
1067                            mPlaybackVolume = value;
1068                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
1069                        }
1070                    } else {
1071                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
1072                    }
1073                    break;
1074                case PLAYBACKINFO_VOLUME_MAX:
1075                    if (value > 0) {
1076                        if (mPlaybackVolumeMax != value) {
1077                            mPlaybackVolumeMax = value;
1078                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
1079                        }
1080                    } else {
1081                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
1082                    }
1083                    break;
1084                case PLAYBACKINFO_USES_STREAM:
1085                    if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
1086                        mPlaybackStream = value;
1087                    } else {
1088                        Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
1089                    }
1090                    break;
1091                case PLAYBACKINFO_VOLUME_HANDLING:
1092                    if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
1093                        if (mPlaybackVolumeHandling != value) {
1094                            mPlaybackVolumeHandling = value;
1095                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
1096                        }
1097                    } else {
1098                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
1099                    }
1100                    break;
1101                default:
1102                    // not throwing an exception or returning an error if more keys are to be
1103                    // supported in the future
1104                    Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
1105                    break;
1106            }
1107        }
1108    }
1109
1110    /**
1111     * @hide
1112     * Return playback information represented as an integer value.
1113     * @param what a key to specify the type of information to retrieve. Valid keys are
1114     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
1115     *        {@link #PLAYBACKINFO_USES_STREAM},
1116     *        {@link #PLAYBACKINFO_VOLUME},
1117     *        {@link #PLAYBACKINFO_VOLUME_MAX},
1118     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
1119     * @return the current value for the given information type, or
1120     *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
1121     *   the value is unknown.
1122     */
1123    public int getIntPlaybackInformation(int what) {
1124        synchronized(mCacheLock) {
1125            switch (what) {
1126                case PLAYBACKINFO_PLAYBACK_TYPE:
1127                    return mPlaybackType;
1128                case PLAYBACKINFO_VOLUME:
1129                    return mPlaybackVolume;
1130                case PLAYBACKINFO_VOLUME_MAX:
1131                    return mPlaybackVolumeMax;
1132                case PLAYBACKINFO_USES_STREAM:
1133                    return mPlaybackStream;
1134                case PLAYBACKINFO_VOLUME_HANDLING:
1135                    return mPlaybackVolumeHandling;
1136                default:
1137                    Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
1138                    return PLAYBACKINFO_INVALID_VALUE;
1139            }
1140        }
1141    }
1142
1143    /**
1144     * Lock for all cached data
1145     */
1146    private final Object mCacheLock = new Object();
1147    /**
1148     * Cache for the playback state.
1149     * Access synchronized on mCacheLock
1150     */
1151    private int mPlaybackState = PLAYSTATE_NONE;
1152    /**
1153     * Time of last play state change
1154     * Access synchronized on mCacheLock
1155     */
1156    private long mPlaybackStateChangeTimeMs = 0;
1157    /**
1158     * Last playback position in ms reported by the user
1159     */
1160    private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
1161    /**
1162     * Last playback speed reported by the user
1163     */
1164    private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
1165    /**
1166     * Cache for the artwork bitmap.
1167     * Access synchronized on mCacheLock
1168     * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
1169     * accessed to be resized, in which case a copy will be made. This would add overhead in
1170     * Bundle operations.
1171     */
1172    private Bitmap mOriginalArtwork;
1173    /**
1174     * Cache for the transport control mask.
1175     * Access synchronized on mCacheLock
1176     */
1177    private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
1178    /**
1179     * Cache for the metadata strings.
1180     * Access synchronized on mCacheLock
1181     * This is re-initialized in apply() and so cannot be final.
1182     */
1183    private Bundle mMetadata = new Bundle();
1184    /**
1185     * Listener registered by user of RemoteControlClient to receive requests for playback position
1186     * update requests.
1187     */
1188    private OnPlaybackPositionUpdateListener mPositionUpdateListener;
1189    /**
1190     * Provider registered by user of RemoteControlClient to provide the current playback position.
1191     */
1192    private OnGetPlaybackPositionListener mPositionProvider;
1193    /**
1194     * Listener registered by user of RemoteControlClient to receive edit changes to metadata
1195     * it exposes.
1196     */
1197    private OnMetadataUpdateListener mMetadataUpdateListener;
1198    /**
1199     * The current remote control client generation ID across the system, as known by this object
1200     */
1201    private int mCurrentClientGenId = -1;
1202    /**
1203     * The remote control client generation ID, the last time it was told it was the current RC.
1204     * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
1205     * client is the "focused" one, and that whenever this client's info is updated, it needs to
1206     * send it to the known IRemoteControlDisplay interfaces.
1207     */
1208    private int mInternalClientGenId = -2;
1209
1210    /**
1211     * The media button intent description associated with this remote control client
1212     * (can / should include target component for intent handling, used when persisting media
1213     *    button event receiver across reboots).
1214     */
1215    private final PendingIntent mRcMediaIntent;
1216
1217    /**
1218     * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true.
1219     */
1220    // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead
1221    private boolean mNeedsPositionSync = false;
1222
1223    /**
1224     * A class to encapsulate all the information about a remote control display.
1225     * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
1226     */
1227    private class DisplayInfoForClient {
1228        /** may never be null */
1229        private IRemoteControlDisplay mRcDisplay;
1230        private int mArtworkExpectedWidth;
1231        private int mArtworkExpectedHeight;
1232        private boolean mWantsPositionSync = false;
1233
1234        DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
1235            mRcDisplay = rcd;
1236            mArtworkExpectedWidth = w;
1237            mArtworkExpectedHeight = h;
1238        }
1239    }
1240
1241    /**
1242     * The list of remote control displays to which this client will send information.
1243     * Accessed and modified synchronized on mCacheLock
1244     */
1245    private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1);
1246
1247    /**
1248     * @hide
1249     * Accessor to media button intent description (includes target component)
1250     */
1251    public PendingIntent getRcMediaIntent() {
1252        return mRcMediaIntent;
1253    }
1254    /**
1255     * @hide
1256     * Accessor to IRemoteControlClient
1257     */
1258    public IRemoteControlClient getIRemoteControlClient() {
1259        return mIRCC;
1260    }
1261
1262    /**
1263     * The IRemoteControlClient implementation
1264     */
1265    private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
1266
1267        public void onInformationRequested(int generationId, int infoFlags) {
1268            // only post messages, we can't block here
1269            if (mEventHandler != null) {
1270                // signal new client
1271                mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
1272                mEventHandler.sendMessage(
1273                        mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
1274                                /*arg1*/ generationId, /*arg2, ignored*/ 0));
1275                // send the information
1276                mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
1277                mEventHandler.removeMessages(MSG_REQUEST_METADATA);
1278                mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
1279                mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
1280                mEventHandler.sendMessage(
1281                        mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
1282                mEventHandler.sendMessage(
1283                        mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
1284                mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
1285                mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
1286            }
1287        }
1288
1289        public void setCurrentClientGenerationId(int clientGeneration) {
1290            // only post messages, we can't block here
1291            if (mEventHandler != null) {
1292                mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
1293                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1294                        MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
1295            }
1296        }
1297
1298        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
1299            // only post messages, we can't block here
1300            if ((mEventHandler != null) && (rcd != null)) {
1301                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1302                        MSG_PLUG_DISPLAY, w, h, rcd));
1303            }
1304        }
1305
1306        public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
1307            // only post messages, we can't block here
1308            if ((mEventHandler != null) && (rcd != null)) {
1309                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1310                        MSG_UNPLUG_DISPLAY, rcd));
1311            }
1312        }
1313
1314        public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
1315            // only post messages, we can't block here
1316            if ((mEventHandler != null) && (rcd != null)) {
1317                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1318                        MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
1319            }
1320        }
1321
1322        public void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync) {
1323            // only post messages, we can't block here
1324            if ((mEventHandler != null) && (rcd != null)) {
1325                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1326                        MSG_DISPLAY_WANTS_POS_SYNC, wantsSync ? 1 : 0, 0/*arg2 ignored*/, rcd));
1327            }
1328        }
1329
1330        public void seekTo(int generationId, long timeMs) {
1331            // only post messages, we can't block here
1332            if (mEventHandler != null) {
1333                mEventHandler.removeMessages(MSG_SEEK_TO);
1334                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1335                        MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */,
1336                        new Long(timeMs)));
1337            }
1338        }
1339
1340        public void updateMetadata(int generationId, int key, long value) {
1341            // only post messages, we can't block here
1342            if (mEventHandler != null) {
1343                mEventHandler.sendMessage(mEventHandler.obtainMessage(
1344                        MSG_UPDATE_METADATA_LONG, generationId /* arg1 */, key /* arg2*/,
1345                        new Long(value)));
1346            }
1347        }
1348    };
1349
1350    /**
1351     * @hide
1352     * Default value for the unique identifier
1353     */
1354    public final static int RCSE_ID_UNREGISTERED = -1;
1355    /**
1356     * Unique identifier of the RemoteControlStackEntry in AudioService with which
1357     * this RemoteControlClient is associated.
1358     */
1359    private int mRcseId = RCSE_ID_UNREGISTERED;
1360    /**
1361     * @hide
1362     * To be only used by AudioManager after it has received the unique id from
1363     * IAudioService.registerRemoteControlClient()
1364     * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
1365     *              this RemoteControlClient is associated.
1366     */
1367    public void setRcseId(int id) {
1368        mRcseId = id;
1369    }
1370
1371    /**
1372     * @hide
1373     */
1374    public int getRcseId() {
1375        return mRcseId;
1376    }
1377
1378    private EventHandler mEventHandler;
1379    private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
1380    private final static int MSG_REQUEST_METADATA = 2;
1381    private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
1382    private final static int MSG_REQUEST_ARTWORK = 4;
1383    private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
1384    private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
1385    private final static int MSG_PLUG_DISPLAY = 7;
1386    private final static int MSG_UNPLUG_DISPLAY = 8;
1387    private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9;
1388    private final static int MSG_SEEK_TO = 10;
1389    private final static int MSG_POSITION_DRIFT_CHECK = 11;
1390    private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12;
1391    private final static int MSG_UPDATE_METADATA_LONG = 13;
1392
1393    private class EventHandler extends Handler {
1394        public EventHandler(RemoteControlClient rcc, Looper looper) {
1395            super(looper);
1396        }
1397
1398        @Override
1399        public void handleMessage(Message msg) {
1400            switch(msg.what) {
1401                case MSG_REQUEST_PLAYBACK_STATE:
1402                    synchronized (mCacheLock) {
1403                        sendPlaybackState_syncCacheLock();
1404                    }
1405                    break;
1406                case MSG_REQUEST_METADATA:
1407                    synchronized (mCacheLock) {
1408                        sendMetadata_syncCacheLock();
1409                    }
1410                    break;
1411                case MSG_REQUEST_TRANSPORTCONTROL:
1412                    synchronized (mCacheLock) {
1413                        sendTransportControlInfo_syncCacheLock();
1414                    }
1415                    break;
1416                case MSG_REQUEST_ARTWORK:
1417                    synchronized (mCacheLock) {
1418                        sendArtwork_syncCacheLock();
1419                    }
1420                    break;
1421                case MSG_NEW_INTERNAL_CLIENT_GEN:
1422                    onNewInternalClientGen(msg.arg1);
1423                    break;
1424                case MSG_NEW_CURRENT_CLIENT_GEN:
1425                    onNewCurrentClientGen(msg.arg1);
1426                    break;
1427                case MSG_PLUG_DISPLAY:
1428                    onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
1429                    break;
1430                case MSG_UNPLUG_DISPLAY:
1431                    onUnplugDisplay((IRemoteControlDisplay)msg.obj);
1432                    break;
1433                case MSG_UPDATE_DISPLAY_ARTWORK_SIZE:
1434                    onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
1435                    break;
1436                case MSG_SEEK_TO:
1437                    onSeekTo(msg.arg1, ((Long)msg.obj).longValue());
1438                    break;
1439                case MSG_POSITION_DRIFT_CHECK:
1440                    onPositionDriftCheck();
1441                    break;
1442                case MSG_DISPLAY_WANTS_POS_SYNC:
1443                    onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
1444                    break;
1445                case MSG_UPDATE_METADATA_LONG:
1446                    onUpdateMetadata(msg.arg1, msg.arg2, ((Long)msg.obj).longValue());
1447                    break;
1448                default:
1449                    Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
1450            }
1451        }
1452    }
1453
1454    //===========================================================
1455    // Communication with the IRemoteControlDisplay (the displays known to the system)
1456
1457    private void sendPlaybackState_syncCacheLock() {
1458        if (mCurrentClientGenId == mInternalClientGenId) {
1459            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1460            while (displayIterator.hasNext()) {
1461                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1462                try {
1463                    di.mRcDisplay.setPlaybackState(mInternalClientGenId,
1464                            mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
1465                            mPlaybackSpeed);
1466                } catch (RemoteException e) {
1467                    Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
1468                    displayIterator.remove();
1469                }
1470            }
1471        }
1472    }
1473
1474    private void sendMetadata_syncCacheLock() {
1475        if (mCurrentClientGenId == mInternalClientGenId) {
1476            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1477            while (displayIterator.hasNext()) {
1478                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1479                try {
1480                    di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
1481                } catch (RemoteException e) {
1482                    Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
1483                    displayIterator.remove();
1484                }
1485            }
1486        }
1487    }
1488
1489    private void sendTransportControlInfo_syncCacheLock() {
1490        if (mCurrentClientGenId == mInternalClientGenId) {
1491            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1492            while (displayIterator.hasNext()) {
1493                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1494                try {
1495                    di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
1496                            mTransportControlFlags, mPlaybackPositionCapabilities);
1497                } catch (RemoteException e) {
1498                    Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
1499                            e);
1500                    displayIterator.remove();
1501                }
1502            }
1503        }
1504    }
1505
1506    private void sendArtwork_syncCacheLock() {
1507        // FIXME modify to cache all requested sizes?
1508        if (mCurrentClientGenId == mInternalClientGenId) {
1509            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1510            while (displayIterator.hasNext()) {
1511                if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
1512                    displayIterator.remove();
1513                }
1514            }
1515        }
1516    }
1517
1518    /**
1519     * Send artwork to an IRemoteControlDisplay.
1520     * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its
1521     *    dimension requirements.
1522     * @return false if there was an error communicating with the IRemoteControlDisplay.
1523     */
1524    private boolean sendArtworkToDisplay(DisplayInfoForClient di) {
1525        if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
1526            Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
1527                    di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
1528            try {
1529                di.mRcDisplay.setArtwork(mInternalClientGenId, artwork);
1530            } catch (RemoteException e) {
1531                Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e);
1532                return false;
1533            }
1534        }
1535        return true;
1536    }
1537
1538    private void sendMetadataWithArtwork_syncCacheLock() {
1539        // FIXME modify to cache all requested sizes?
1540        if (mCurrentClientGenId == mInternalClientGenId) {
1541            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1542            while (displayIterator.hasNext()) {
1543                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1544                try {
1545                    if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
1546                        Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
1547                                di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
1548                        di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
1549                    } else {
1550                        di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
1551                    }
1552                } catch (RemoteException e) {
1553                    Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
1554                    displayIterator.remove();
1555                }
1556            }
1557        }
1558    }
1559
1560    //===========================================================
1561    // Communication with AudioService
1562
1563    private static IAudioService sService;
1564
1565    private static IAudioService getService()
1566    {
1567        if (sService != null) {
1568            return sService;
1569        }
1570        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
1571        sService = IAudioService.Stub.asInterface(b);
1572        return sService;
1573    }
1574
1575    private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
1576        if (mRcseId == RCSE_ID_UNREGISTERED) {
1577            return;
1578        }
1579        //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
1580        IAudioService service = getService();
1581        try {
1582            service.setPlaybackInfoForRcc(mRcseId, what, value);
1583        } catch (RemoteException e) {
1584            Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e);
1585        }
1586    }
1587
1588    private void sendAudioServiceNewPlaybackState_syncCacheLock() {
1589        if (mRcseId == RCSE_ID_UNREGISTERED) {
1590            return;
1591        }
1592        IAudioService service = getService();
1593        try {
1594            service.setPlaybackStateForRcc(mRcseId,
1595                    mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed);
1596        } catch (RemoteException e) {
1597            Log.e(TAG, "Dead object in setPlaybackStateForRcc", e);
1598        }
1599    }
1600
1601    //===========================================================
1602    // Message handlers
1603
1604    private void onNewInternalClientGen(int clientGeneration) {
1605        synchronized (mCacheLock) {
1606            // this remote control client is told it is the "focused" one:
1607            // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
1608            mInternalClientGenId = clientGeneration;
1609        }
1610    }
1611
1612    private void onNewCurrentClientGen(int clientGeneration) {
1613        synchronized (mCacheLock) {
1614            mCurrentClientGenId = clientGeneration;
1615        }
1616    }
1617
1618    /** pre-condition rcd != null */
1619    private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) {
1620        synchronized(mCacheLock) {
1621            // do we have this display already?
1622            boolean displayKnown = false;
1623            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1624            while (displayIterator.hasNext() && !displayKnown) {
1625                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1626                displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
1627                if (displayKnown) {
1628                    // this display was known but the change in artwork size will cause the
1629                    // artwork to be refreshed
1630                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
1631                        di.mArtworkExpectedWidth = w;
1632                        di.mArtworkExpectedHeight = h;
1633                        if (!sendArtworkToDisplay(di)) {
1634                            displayIterator.remove();
1635                        }
1636                    }
1637                }
1638            }
1639            if (!displayKnown) {
1640                mRcDisplays.add(new DisplayInfoForClient(rcd, w, h));
1641            }
1642        }
1643    }
1644
1645    /** pre-condition rcd != null */
1646    private void onUnplugDisplay(IRemoteControlDisplay rcd) {
1647        synchronized(mCacheLock) {
1648            Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1649            while (displayIterator.hasNext()) {
1650                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1651                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1652                    displayIterator.remove();
1653                    break;
1654                }
1655            }
1656            // list of RCDs has changed, reevaluate whether position check is still needed
1657            boolean oldNeedsPositionSync = mNeedsPositionSync;
1658            boolean newNeedsPositionSync = false;
1659            displayIterator = mRcDisplays.iterator();
1660            while (displayIterator.hasNext()) {
1661                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1662                if (di.mWantsPositionSync) {
1663                    newNeedsPositionSync = true;
1664                    break;
1665                }
1666            }
1667            mNeedsPositionSync = newNeedsPositionSync;
1668            if (oldNeedsPositionSync != mNeedsPositionSync) {
1669                // update needed?
1670                initiateCheckForDrift_syncCacheLock();
1671            }
1672        }
1673    }
1674
1675    /** pre-condition rcd != null */
1676    private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) {
1677        synchronized(mCacheLock) {
1678            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1679            while (displayIterator.hasNext()) {
1680                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1681                if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
1682                        ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
1683                    di.mArtworkExpectedWidth = w;
1684                    di.mArtworkExpectedHeight = h;
1685                    if (!sendArtworkToDisplay(di)) {
1686                        displayIterator.remove();
1687                    }
1688                    break;
1689                }
1690            }
1691        }
1692    }
1693
1694    /** pre-condition rcd != null */
1695    private void onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync) {
1696        synchronized(mCacheLock) {
1697            boolean oldNeedsPositionSync = mNeedsPositionSync;
1698            boolean newNeedsPositionSync = false;
1699            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1700            // go through the list of RCDs and for each entry, check both whether this is the RCD
1701            //  that gets upated, and whether the list has one entry that wants position sync
1702            while (displayIterator.hasNext()) {
1703                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1704                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1705                    di.mWantsPositionSync = wantsSync;
1706                }
1707                if (di.mWantsPositionSync) {
1708                    newNeedsPositionSync = true;
1709                }
1710            }
1711            mNeedsPositionSync = newNeedsPositionSync;
1712            if (oldNeedsPositionSync != mNeedsPositionSync) {
1713                // update needed?
1714                initiateCheckForDrift_syncCacheLock();
1715            }
1716        }
1717    }
1718
1719    private void onSeekTo(int generationId, long timeMs) {
1720        synchronized (mCacheLock) {
1721            if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
1722                mPositionUpdateListener.onPlaybackPositionUpdate(timeMs);
1723            }
1724        }
1725    }
1726
1727    private void onUpdateMetadata(int generationId, int key, long value) {
1728        synchronized (mCacheLock) {
1729            if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
1730                mMetadataUpdateListener.onMetadataUpdateLong(key, value);
1731            }
1732        }
1733    }
1734
1735    //===========================================================
1736    // Internal utilities
1737
1738    /**
1739     * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
1740     * If the bitmap fits, then do nothing and return the original.
1741     *
1742     * @param bitmap
1743     * @param maxWidth
1744     * @param maxHeight
1745     * @return
1746     */
1747
1748    private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
1749        if (bitmap != null) {
1750            final int width = bitmap.getWidth();
1751            final int height = bitmap.getHeight();
1752            if (width > maxWidth || height > maxHeight) {
1753                float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
1754                int newWidth = Math.round(scale * width);
1755                int newHeight = Math.round(scale * height);
1756                Bitmap.Config newConfig = bitmap.getConfig();
1757                if (newConfig == null) {
1758                    newConfig = Bitmap.Config.ARGB_8888;
1759                }
1760                Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
1761                Canvas canvas = new Canvas(outBitmap);
1762                Paint paint = new Paint();
1763                paint.setAntiAlias(true);
1764                paint.setFilterBitmap(true);
1765                canvas.drawBitmap(bitmap, null,
1766                        new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
1767                bitmap = outBitmap;
1768            }
1769        }
1770        return bitmap;
1771    }
1772
1773    /**
1774     *  Fast routine to go through an array of allowed keys and return whether the key is part
1775     *  of that array
1776     * @param key the key value
1777     * @param validKeys the array of valid keys for a given type
1778     * @return true if the key is part of the array, false otherwise
1779     */
1780    private static boolean validTypeForKey(int key, int[] validKeys) {
1781        try {
1782            for (int i = 0 ; ; i++) {
1783                if (key == validKeys[i]) {
1784                    return true;
1785                }
1786            }
1787        } catch (ArrayIndexOutOfBoundsException e) {
1788            return false;
1789        }
1790    }
1791
1792    /**
1793     * Returns whether, for the given playback state, the playback position is expected to
1794     * be changing.
1795     * @param playstate the playback state to evaluate
1796     * @return true during any form of playback, false if it's not playing anything while in this
1797     *     playback state
1798     */
1799    private static boolean playbackPositionShouldMove(int playstate) {
1800        switch(playstate) {
1801            case PLAYSTATE_STOPPED:
1802            case PLAYSTATE_PAUSED:
1803            case PLAYSTATE_BUFFERING:
1804            case PLAYSTATE_ERROR:
1805            case PLAYSTATE_SKIPPING_FORWARDS:
1806            case PLAYSTATE_SKIPPING_BACKWARDS:
1807                return false;
1808            case PLAYSTATE_PLAYING:
1809            case PLAYSTATE_FAST_FORWARDING:
1810            case PLAYSTATE_REWINDING:
1811            default:
1812                return true;
1813        }
1814    }
1815
1816    /**
1817     * Period for playback position drift checks, 15s when playing at 1x or slower.
1818     */
1819    private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
1820    /**
1821     * Minimum period for playback position drift checks, never more often when every 2s, when
1822     * fast forwarding or rewinding.
1823     */
1824    private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
1825    /**
1826     * The value above which the difference between client-reported playback position and
1827     * estimated position is considered a drift.
1828     */
1829    private final static long POSITION_DRIFT_MAX_MS = 500;
1830    /**
1831     * Compute the period at which the estimated playback position should be compared against the
1832     * actual playback position. Is a funciton of playback speed.
1833     * @param speed 1.0f is normal playback speed
1834     * @return the period in ms
1835     */
1836    private static long getCheckPeriodFromSpeed(float speed) {
1837        if (Math.abs(speed) <= 1.0f) {
1838            return POSITION_REFRESH_PERIOD_PLAYING_MS;
1839        } else {
1840            return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)),
1841                    POSITION_REFRESH_PERIOD_MIN_MS);
1842        }
1843    }
1844}
1845