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