RemoteControlClient.java revision bc43b4c2f24fd03c0d0546895c97918c1736d9fb
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
69    /**
70     * Playback state of a RemoteControlClient which is stopped.
71     *
72     * @see #setPlaybackState(int)
73     */
74    public final static int PLAYSTATE_STOPPED            = 1;
75    /**
76     * Playback state of a RemoteControlClient which is paused.
77     *
78     * @see #setPlaybackState(int)
79     */
80    public final static int PLAYSTATE_PAUSED             = 2;
81    /**
82     * Playback state of a RemoteControlClient which is playing media.
83     *
84     * @see #setPlaybackState(int)
85     */
86    public final static int PLAYSTATE_PLAYING            = 3;
87    /**
88     * Playback state of a RemoteControlClient which is fast forwarding in the media
89     *    it is currently playing.
90     *
91     * @see #setPlaybackState(int)
92     */
93    public final static int PLAYSTATE_FAST_FORWARDING    = 4;
94    /**
95     * Playback state of a RemoteControlClient which is fast rewinding in the media
96     *    it is currently playing.
97     *
98     * @see #setPlaybackState(int)
99     */
100    public final static int PLAYSTATE_REWINDING          = 5;
101    /**
102     * Playback state of a RemoteControlClient which is skipping to the next
103     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
104     *
105     * @see #setPlaybackState(int)
106     */
107    public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
108    /**
109     * Playback state of a RemoteControlClient which is skipping back to the previous
110     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
111     *
112     * @see #setPlaybackState(int)
113     */
114    public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
115    /**
116     * Playback state of a RemoteControlClient which is buffering data to play before it can
117     *    start or resume playback.
118     *
119     * @see #setPlaybackState(int)
120     */
121    public final static int PLAYSTATE_BUFFERING          = 8;
122    /**
123     * Playback state of a RemoteControlClient which cannot perform any playback related
124     *    operation because of an internal error. Examples of such situations are no network
125     *    connectivity when attempting to stream data from a server, or expired user credentials
126     *    when trying to play subscription-based content.
127     *
128     * @see #setPlaybackState(int)
129     */
130    public final static int PLAYSTATE_ERROR              = 9;
131    /**
132     * @hide
133     * The value of a playback state when none has been declared.
134     * Intentionally hidden as an application shouldn't set such a playback state value.
135     */
136    public final static int PLAYSTATE_NONE               = 0;
137
138    /**
139     * @hide
140     * The default playback type, "local", indicating the presentation of the media is happening on
141     * the same device (e.g. a phone, a tablet) as where it is controlled from.
142     */
143    public final static int PLAYBACK_TYPE_LOCAL = 0;
144    /**
145     * @hide
146     * A playback type indicating the presentation of the media is happening on
147     * a different device (i.e. the remote device) than where it is controlled from.
148     */
149    public final static int PLAYBACK_TYPE_REMOTE = 1;
150    private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
151    private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
152    /**
153     * @hide
154     * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
155     * from this object. An example of fixed playback volume is a remote player, playing over HDMI
156     * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
157     * source.
158     * @see #PLAYBACKINFO_VOLUME_HANDLING.
159     */
160    public final static int PLAYBACK_VOLUME_FIXED = 0;
161    /**
162     * @hide
163     * Playback information indicating the playback volume is variable and can be controlled from
164     * this object.
165     * @see #PLAYBACKINFO_VOLUME_HANDLING.
166     */
167    public final static int PLAYBACK_VOLUME_VARIABLE = 1;
168    /**
169     * @hide (to be un-hidden)
170     * The playback information value indicating the value of a given information type is invalid.
171     * @see #PLAYBACKINFO_VOLUME_HANDLING.
172     */
173    public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
174
175    /**
176     * @hide
177     * An unknown or invalid playback position value.
178     */
179    public final static long PLAYBACK_POSITION_INVALID = -1;
180    /**
181     * @hide
182     * The default playback speed, 1x.
183     */
184    public final static float PLAYBACK_SPEED_1X = 1.0f;
185
186    //==========================================
187    // Public keys for playback information
188    /**
189     * @hide
190     * Playback information that defines the type of playback associated with this
191     * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
192     */
193    public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
194    /**
195     * @hide
196     * Playback information that defines at what volume the playback associated with this
197     * RemoteControlClient is performed. This information is only used when the playback type is not
198     * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
199     */
200    public final static int PLAYBACKINFO_VOLUME = 2;
201    /**
202     * @hide
203     * Playback information that defines the maximum volume volume value that is supported
204     * by the playback associated with this RemoteControlClient. This information is only used
205     * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
206     */
207    public final static int PLAYBACKINFO_VOLUME_MAX = 3;
208    /**
209     * @hide
210     * Playback information that defines how volume is handled for the presentation of the media.
211     * @see #PLAYBACK_VOLUME_FIXED
212     * @see #PLAYBACK_VOLUME_VARIABLE
213     */
214    public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
215    /**
216     * @hide
217     * Playback information that defines over what stream type the media is presented.
218     */
219    public final static int PLAYBACKINFO_USES_STREAM = 5;
220
221    //==========================================
222    // Public flags for the supported transport control capabililities
223    /**
224     * Flag indicating a RemoteControlClient makes use of the "previous" media key.
225     *
226     * @see #setTransportControlFlags(int)
227     * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
228     */
229    public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
230    /**
231     * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
232     *
233     * @see #setTransportControlFlags(int)
234     * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
235     */
236    public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
237    /**
238     * Flag indicating a RemoteControlClient makes use of the "play" media key.
239     *
240     * @see #setTransportControlFlags(int)
241     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
242     */
243    public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
244    /**
245     * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
246     *
247     * @see #setTransportControlFlags(int)
248     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
249     */
250    public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
251    /**
252     * Flag indicating a RemoteControlClient makes use of the "pause" media key.
253     *
254     * @see #setTransportControlFlags(int)
255     * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
256     */
257    public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
258    /**
259     * Flag indicating a RemoteControlClient makes use of the "stop" media key.
260     *
261     * @see #setTransportControlFlags(int)
262     * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
263     */
264    public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
265    /**
266     * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
267     *
268     * @see #setTransportControlFlags(int)
269     * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
270     */
271    public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
272    /**
273     * Flag indicating a RemoteControlClient makes use of the "next" media key.
274     *
275     * @see #setTransportControlFlags(int)
276     * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
277     */
278    public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
279    /**
280     * @hide
281     * (to be un-hidden and added in javadoc of setTransportControlFlags(int))
282     * Flag indicating a RemoteControlClient can receive changes in the media playback position
283     * through the {@link #OnPlaybackPositionUpdateListener} interface.
284     *
285     * @see #setTransportControlFlags(int)
286     */
287    public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
288
289    /**
290     * @hide
291     * The flags for when no media keys are declared supported.
292     * Intentionally hidden as an application shouldn't set the transport control flags
293     *     to this value.
294     */
295    public final static int FLAGS_KEY_MEDIA_NONE = 0;
296
297    /**
298     * @hide
299     * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
300     */
301    public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
302    /**
303     * @hide
304     * Flag used to signal that the transport control buttons supported by the
305     *     RemoteControlClient are requested.
306     * This can for instance happen when playback is at the end of a playlist, and the "next"
307     * operation is not supported anymore.
308     */
309    public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
310    /**
311     * @hide
312     * Flag used to signal that the playback state of the RemoteControlClient is requested.
313     */
314    public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
315    /**
316     * @hide
317     * Flag used to signal that the album art for the RemoteControlClient is requested.
318     */
319    public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
320
321    /**
322     * Class constructor.
323     * @param mediaButtonIntent The intent that will be sent for the media button events sent
324     *     by remote controls.
325     *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
326     *     action, and have a component that will handle the intent (set with
327     *     {@link Intent#setComponent(ComponentName)}) registered with
328     *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
329     *     before this new RemoteControlClient can itself be registered with
330     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
331     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
332     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
333     */
334    public RemoteControlClient(PendingIntent mediaButtonIntent) {
335        mRcMediaIntent = mediaButtonIntent;
336
337        Looper looper;
338        if ((looper = Looper.myLooper()) != null) {
339            mEventHandler = new EventHandler(this, looper);
340        } else if ((looper = Looper.getMainLooper()) != null) {
341            mEventHandler = new EventHandler(this, looper);
342        } else {
343            mEventHandler = null;
344            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
345        }
346    }
347
348    /**
349     * Class constructor for a remote control client whose internal event handling
350     * happens on a user-provided Looper.
351     * @param mediaButtonIntent The intent that will be sent for the media button events sent
352     *     by remote controls.
353     *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
354     *     action, and have a component that will handle the intent (set with
355     *     {@link Intent#setComponent(ComponentName)}) registered with
356     *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
357     *     before this new RemoteControlClient can itself be registered with
358     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
359     * @param looper The Looper running the event loop.
360     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
361     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
362     */
363    public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
364        mRcMediaIntent = mediaButtonIntent;
365
366        mEventHandler = new EventHandler(this, looper);
367    }
368
369    private static final int[] METADATA_KEYS_TYPE_STRING = {
370        MediaMetadataRetriever.METADATA_KEY_ALBUM,
371        MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
372        MediaMetadataRetriever.METADATA_KEY_TITLE,
373        MediaMetadataRetriever.METADATA_KEY_ARTIST,
374        MediaMetadataRetriever.METADATA_KEY_AUTHOR,
375        MediaMetadataRetriever.METADATA_KEY_COMPILATION,
376        MediaMetadataRetriever.METADATA_KEY_COMPOSER,
377        MediaMetadataRetriever.METADATA_KEY_DATE,
378        MediaMetadataRetriever.METADATA_KEY_GENRE,
379        MediaMetadataRetriever.METADATA_KEY_TITLE,
380        MediaMetadataRetriever.METADATA_KEY_WRITER };
381    private static final int[] METADATA_KEYS_TYPE_LONG = {
382        MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
383        MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
384        MediaMetadataRetriever.METADATA_KEY_DURATION };
385
386    /**
387     * Class used to modify metadata in a {@link RemoteControlClient} object.
388     * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
389     * on which you set the metadata for the RemoteControlClient instance. Once all the information
390     * has been set, use {@link #apply()} to make it the new metadata that should be displayed
391     * for the associated client. Once the metadata has been "applied", you cannot reuse this
392     * instance of the MetadataEditor.
393     */
394    public class MetadataEditor {
395        /**
396         * @hide
397         */
398        protected boolean mMetadataChanged;
399        /**
400         * @hide
401         */
402        protected boolean mArtworkChanged;
403        /**
404         * @hide
405         */
406        protected Bitmap mEditorArtwork;
407        /**
408         * @hide
409         */
410        protected Bundle mEditorMetadata;
411        private boolean mApplied = false;
412
413        // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
414        private MetadataEditor() { }
415        /**
416         * @hide
417         */
418        public Object clone() throws CloneNotSupportedException {
419            throw new CloneNotSupportedException();
420        }
421
422        /**
423         * The metadata key for the content artwork / album art.
424         */
425        public final static int BITMAP_KEY_ARTWORK = 100;
426        /**
427         * @hide
428         * TODO(jmtrivi) have lockscreen and music move to the new key name
429         */
430        public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
431
432        /**
433         * Adds textual information to be displayed.
434         * Note that none of the information added after {@link #apply()} has been called,
435         * will be displayed.
436         * @param key The identifier of a the metadata field to set. Valid values are
437         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
438         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
439         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
440         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
441         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
442         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
443         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
444         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
445         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
446         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
447         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
448         * @param value The text for the given key, or {@code null} to signify there is no valid
449         *      information for the field.
450         * @return Returns a reference to the same MetadataEditor object, so you can chain put
451         *      calls together.
452         */
453        public synchronized MetadataEditor putString(int key, String value)
454                throws IllegalArgumentException {
455            if (mApplied) {
456                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
457                return this;
458            }
459            if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
460                throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
461            }
462            mEditorMetadata.putString(String.valueOf(key), value);
463            mMetadataChanged = true;
464            return this;
465        }
466
467        /**
468         * Adds numerical information to be displayed.
469         * Note that none of the information added after {@link #apply()} has been called,
470         * will be displayed.
471         * @param key the identifier of a the metadata field to set. Valid values are
472         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
473         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
474         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
475         *      expressed in milliseconds),
476         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
477         * @param value The long value for the given key
478         * @return Returns a reference to the same MetadataEditor object, so you can chain put
479         *      calls together.
480         * @throws IllegalArgumentException
481         */
482        public synchronized MetadataEditor putLong(int key, long value)
483                throws IllegalArgumentException {
484            if (mApplied) {
485                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
486                return this;
487            }
488            if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
489                throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
490            }
491            mEditorMetadata.putLong(String.valueOf(key), value);
492            mMetadataChanged = true;
493            return this;
494        }
495
496        /**
497         * Sets the album / artwork picture to be displayed on the remote control.
498         * @param key the identifier of the bitmap to set. The only valid value is
499         *      {@link #BITMAP_KEY_ARTWORK}
500         * @param bitmap The bitmap for the artwork, or null if there isn't any.
501         * @return Returns a reference to the same MetadataEditor object, so you can chain put
502         *      calls together.
503         * @throws IllegalArgumentException
504         * @see android.graphics.Bitmap
505         */
506        public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
507                throws IllegalArgumentException {
508            if (mApplied) {
509                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
510                return this;
511            }
512            if (key != BITMAP_KEY_ARTWORK) {
513                throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
514            }
515            mEditorArtwork = bitmap;
516            mArtworkChanged = true;
517            return this;
518        }
519
520        /**
521         * Clears all the metadata that has been set since the MetadataEditor instance was
522         *     created with {@link RemoteControlClient#editMetadata(boolean)}.
523         */
524        public synchronized void clear() {
525            if (mApplied) {
526                Log.e(TAG, "Can't clear a previously applied MetadataEditor");
527                return;
528            }
529            mEditorMetadata.clear();
530            mEditorArtwork = null;
531        }
532
533        /**
534         * Associates all the metadata that has been set since the MetadataEditor instance was
535         *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
536         *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
537         *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
538         */
539        public synchronized void apply() {
540            if (mApplied) {
541                Log.e(TAG, "Can't apply a previously applied MetadataEditor");
542                return;
543            }
544            synchronized(mCacheLock) {
545                // assign the edited data
546                mMetadata = new Bundle(mEditorMetadata);
547                if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
548                    mOriginalArtwork.recycle();
549                }
550                mOriginalArtwork = mEditorArtwork;
551                mEditorArtwork = null;
552                if (mMetadataChanged & mArtworkChanged) {
553                    // send to remote control display if conditions are met
554                    sendMetadataWithArtwork_syncCacheLock();
555                } else if (mMetadataChanged) {
556                    // send to remote control display if conditions are met
557                    sendMetadata_syncCacheLock();
558                } else if (mArtworkChanged) {
559                    // send to remote control display if conditions are met
560                    sendArtwork_syncCacheLock();
561                }
562                mApplied = true;
563            }
564        }
565    }
566
567    /**
568     * Creates a {@link MetadataEditor}.
569     * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
570     *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
571     * @return a new MetadataEditor instance.
572     */
573    public MetadataEditor editMetadata(boolean startEmpty) {
574        MetadataEditor editor = new MetadataEditor();
575        if (startEmpty) {
576            editor.mEditorMetadata = new Bundle();
577            editor.mEditorArtwork = null;
578            editor.mMetadataChanged = true;
579            editor.mArtworkChanged = true;
580        } else {
581            editor.mEditorMetadata = new Bundle(mMetadata);
582            editor.mEditorArtwork = mOriginalArtwork;
583            editor.mMetadataChanged = false;
584            editor.mArtworkChanged = false;
585        }
586        return editor;
587    }
588
589    /**
590     * Sets the current playback state.
591     * @param state The current playback state, one of the following values:
592     *       {@link #PLAYSTATE_STOPPED},
593     *       {@link #PLAYSTATE_PAUSED},
594     *       {@link #PLAYSTATE_PLAYING},
595     *       {@link #PLAYSTATE_FAST_FORWARDING},
596     *       {@link #PLAYSTATE_REWINDING},
597     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
598     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
599     *       {@link #PLAYSTATE_BUFFERING},
600     *       {@link #PLAYSTATE_ERROR}.
601     */
602    public void setPlaybackState(int state) {
603        setPlaybackState(state, PLAYBACK_POSITION_INVALID, PLAYBACK_SPEED_1X);
604    }
605
606    /**
607     * @hide
608     * (to be un-hidden)
609     * Sets the current playback state and the matching media position for the current playback
610     *   speed.
611     * @param state The current playback state, one of the following values:
612     *       {@link #PLAYSTATE_STOPPED},
613     *       {@link #PLAYSTATE_PAUSED},
614     *       {@link #PLAYSTATE_PLAYING},
615     *       {@link #PLAYSTATE_FAST_FORWARDING},
616     *       {@link #PLAYSTATE_REWINDING},
617     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
618     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
619     *       {@link #PLAYSTATE_BUFFERING},
620     *       {@link #PLAYSTATE_ERROR}.
621     * @param timeInMs a 0 or positive value for the current media position expressed in ms
622     *    (same unit as for when sending the media duration, if applicable, with
623     *    {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the
624     *    {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not
625     *    known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state
626     *    is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
627     * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
628     *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
629     *    playing (e.g. when state is {@link #PLAYSTATE_ERROR}).
630     */
631    public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
632        synchronized(mCacheLock) {
633            if (timeInMs != PLAYBACK_POSITION_INVALID) {
634                mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE;
635            } else {
636                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE;
637            }
638            if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
639                    || (mPlaybackSpeed != playbackSpeed)) {
640                // store locally
641                mPlaybackState = state;
642                mPlaybackPositionMs = timeInMs;
643                mPlaybackSpeed = playbackSpeed;
644                // keep track of when the state change occurred
645                mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
646
647                // send to remote control display if conditions are met
648                sendPlaybackState_syncCacheLock();
649                // update AudioService
650                sendAudioServiceNewPlaybackState_syncCacheLock();
651            }
652        }
653    }
654
655    /**
656     * Sets the flags for the media transport control buttons that this client supports.
657     * @param transportControlFlags A combination of the following flags:
658     *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
659     *      {@link #FLAG_KEY_MEDIA_REWIND},
660     *      {@link #FLAG_KEY_MEDIA_PLAY},
661     *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
662     *      {@link #FLAG_KEY_MEDIA_PAUSE},
663     *      {@link #FLAG_KEY_MEDIA_STOP},
664     *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
665     *      {@link #FLAG_KEY_MEDIA_NEXT}
666     */
667    public void setTransportControlFlags(int transportControlFlags) {
668        synchronized(mCacheLock) {
669            // store locally
670            mTransportControlFlags = transportControlFlags;
671
672            // send to remote control display if conditions are met
673            sendTransportControlFlags_syncCacheLock();
674        }
675    }
676
677    /**
678     * @hide
679     * (to be un-hidden)
680     * Interface definition for a callback to be invoked when the media playback position is
681     * requested to be updated.
682     */
683    public interface OnPlaybackPositionUpdateListener {
684        /**
685         * Called on the listener to notify it that the playback head should be set at the given
686         * position. If the position can be changed from its current value, the implementor of
687         * the interface should also update the playback position using
688         * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new
689         * position being used, regardless of whether it differs from the requested position.
690         * @param newPositionMs the new requested position in the current media, expressed in ms.
691         */
692        void onPlaybackPositionUpdate(long newPositionMs);
693    }
694
695    /**
696     * @hide
697     * (to be un-hidden)
698     * Sets the listener RemoteControlClient calls whenever the media playback position is requested
699     * to be updated.
700     * Notifications will be received in the same thread as the one in which RemoteControlClient
701     * was created.
702     * @param l
703     */
704    public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
705        synchronized(mCacheLock) {
706            if ((mPositionUpdateListener == null) && (l != null)) {
707                mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE;
708                // tell RCDs and AudioService this RCC accepts position updates
709                // TODO implement
710            } else if ((mPositionUpdateListener != null) && (l == null)) {
711                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE;
712                // tell RCDs and AudioService this RCC doesn't handle position updates
713                // TODO implement
714            }
715            mPositionUpdateListener = l;
716        }
717    }
718
719    /**
720     * @hide
721     * Flag to reflect that the application controlling this RemoteControlClient sends playback
722     * position updates. The playback position being "readable" is considered from the application's
723     * point of view.
724     */
725    public static int MEDIA_POSITION_READABLE = 1 << 0;
726    /**
727     * @hide
728     * Flag to reflect that the application controlling this RemoteControlClient can receive
729     * playback position updates. The playback position being "writable"
730     * is considered from the application's point of view.
731     */
732    public static int MEDIA_POSITION_WRITABLE = 1 << 1;
733
734    private int mPlaybackPositionCapabilities = 0;
735
736    /** @hide */
737    public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
738    /** @hide */
739    // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
740    public final static int DEFAULT_PLAYBACK_VOLUME = 15;
741
742    private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
743    private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
744    private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
745    private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
746    private int mPlaybackStream = AudioManager.STREAM_MUSIC;
747
748    /**
749     * @hide
750     * Set information describing information related to the playback of media so the system
751     * can implement additional behavior to handle non-local playback usecases.
752     * @param what a key to specify the type of information to set. Valid keys are
753     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
754     *        {@link #PLAYBACKINFO_USES_STREAM},
755     *        {@link #PLAYBACKINFO_VOLUME},
756     *        {@link #PLAYBACKINFO_VOLUME_MAX},
757     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
758     * @param value the value for the supplied information to set.
759     */
760    public void setPlaybackInformation(int what, int value) {
761        synchronized(mCacheLock) {
762            switch (what) {
763                case PLAYBACKINFO_PLAYBACK_TYPE:
764                    if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
765                        if (mPlaybackType != value) {
766                            mPlaybackType = value;
767                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
768                        }
769                    } else {
770                        Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
771                    }
772                    break;
773                case PLAYBACKINFO_VOLUME:
774                    if ((value > -1) && (value <= mPlaybackVolumeMax)) {
775                        if (mPlaybackVolume != value) {
776                            mPlaybackVolume = value;
777                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
778                        }
779                    } else {
780                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
781                    }
782                    break;
783                case PLAYBACKINFO_VOLUME_MAX:
784                    if (value > 0) {
785                        if (mPlaybackVolumeMax != value) {
786                            mPlaybackVolumeMax = value;
787                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
788                        }
789                    } else {
790                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
791                    }
792                    break;
793                case PLAYBACKINFO_USES_STREAM:
794                    if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
795                        mPlaybackStream = value;
796                    } else {
797                        Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
798                    }
799                    break;
800                case PLAYBACKINFO_VOLUME_HANDLING:
801                    if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
802                        if (mPlaybackVolumeHandling != value) {
803                            mPlaybackVolumeHandling = value;
804                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
805                        }
806                    } else {
807                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
808                    }
809                    break;
810                default:
811                    // not throwing an exception or returning an error if more keys are to be
812                    // supported in the future
813                    Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
814                    break;
815            }
816        }
817    }
818
819    /**
820     * @hide
821     * Return playback information represented as an integer value.
822     * @param what a key to specify the type of information to retrieve. Valid keys are
823     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
824     *        {@link #PLAYBACKINFO_USES_STREAM},
825     *        {@link #PLAYBACKINFO_VOLUME},
826     *        {@link #PLAYBACKINFO_VOLUME_MAX},
827     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
828     * @return the current value for the given information type, or
829     *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
830     *   the value is unknown.
831     */
832    public int getIntPlaybackInformation(int what) {
833        synchronized(mCacheLock) {
834            switch (what) {
835                case PLAYBACKINFO_PLAYBACK_TYPE:
836                    return mPlaybackType;
837                case PLAYBACKINFO_VOLUME:
838                    return mPlaybackVolume;
839                case PLAYBACKINFO_VOLUME_MAX:
840                    return mPlaybackVolumeMax;
841                case PLAYBACKINFO_USES_STREAM:
842                    return mPlaybackStream;
843                case PLAYBACKINFO_VOLUME_HANDLING:
844                    return mPlaybackVolumeHandling;
845                default:
846                    Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
847                    return PLAYBACKINFO_INVALID_VALUE;
848            }
849        }
850    }
851
852    /**
853     * Lock for all cached data
854     */
855    private final Object mCacheLock = new Object();
856    /**
857     * Cache for the playback state.
858     * Access synchronized on mCacheLock
859     */
860    private int mPlaybackState = PLAYSTATE_NONE;
861    /**
862     * Time of last play state change
863     * Access synchronized on mCacheLock
864     */
865    private long mPlaybackStateChangeTimeMs = 0;
866    /**
867     * Last playback position in ms reported by the user
868     */
869    private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
870    /**
871     * Last playback speed reported by the user
872     */
873    private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
874    /**
875     * Cache for the artwork bitmap.
876     * Access synchronized on mCacheLock
877     * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
878     * accessed to be resized, in which case a copy will be made. This would add overhead in
879     * Bundle operations.
880     */
881    private Bitmap mOriginalArtwork;
882    /**
883     * Cache for the transport control mask.
884     * Access synchronized on mCacheLock
885     */
886    private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
887    /**
888     * Cache for the metadata strings.
889     * Access synchronized on mCacheLock
890     * This is re-initialized in apply() and so cannot be final.
891     */
892    private Bundle mMetadata = new Bundle();
893    /**
894     * Listener registered by user of RemoteControlClient to receive requests for playback position
895     * update requests.
896     */
897    private OnPlaybackPositionUpdateListener mPositionUpdateListener;
898    /**
899     * The current remote control client generation ID across the system, as known by this object
900     */
901    private int mCurrentClientGenId = -1;
902    /**
903     * The remote control client generation ID, the last time it was told it was the current RC.
904     * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
905     * client is the "focused" one, and that whenever this client's info is updated, it needs to
906     * send it to the known IRemoteControlDisplay interfaces.
907     */
908    private int mInternalClientGenId = -2;
909
910    /**
911     * The media button intent description associated with this remote control client
912     * (can / should include target component for intent handling, used when persisting media
913     *    button event receiver across reboots).
914     */
915    private final PendingIntent mRcMediaIntent;
916
917    /**
918     * A class to encapsulate all the information about a remote control display.
919     * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
920     */
921    private class DisplayInfoForClient {
922        /** may never be null */
923        private IRemoteControlDisplay mRcDisplay;
924        private int mArtworkExpectedWidth;
925        private int mArtworkExpectedHeight;
926
927        DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
928            mRcDisplay = rcd;
929            mArtworkExpectedWidth = w;
930            mArtworkExpectedHeight = h;
931        }
932    }
933
934    /**
935     * The list of remote control displays to which this client will send information.
936     * Accessed and modified synchronized on mCacheLock
937     */
938    private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1);
939
940    /**
941     * @hide
942     * Accessor to media button intent description (includes target component)
943     */
944    public PendingIntent getRcMediaIntent() {
945        return mRcMediaIntent;
946    }
947    /**
948     * @hide
949     * Accessor to IRemoteControlClient
950     */
951    public IRemoteControlClient getIRemoteControlClient() {
952        return mIRCC;
953    }
954
955    /**
956     * The IRemoteControlClient implementation
957     */
958    private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
959
960        public void onInformationRequested(int clientGeneration, int infoFlags) {
961            // only post messages, we can't block here
962            if (mEventHandler != null) {
963                // signal new client
964                mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
965                mEventHandler.dispatchMessage(
966                        mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
967                                /*arg1*/ clientGeneration, /*arg2, ignored*/ 0));
968                // send the information
969                mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
970                mEventHandler.removeMessages(MSG_REQUEST_METADATA);
971                mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
972                mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
973                mEventHandler.dispatchMessage(
974                        mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
975                mEventHandler.dispatchMessage(
976                        mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
977                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
978                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
979            }
980        }
981
982        public void setCurrentClientGenerationId(int clientGeneration) {
983            // only post messages, we can't block here
984            if (mEventHandler != null) {
985                mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
986                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
987                        MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
988            }
989        }
990
991        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
992            // only post messages, we can't block here
993            if ((mEventHandler != null) && (rcd != null)) {
994                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
995                        MSG_PLUG_DISPLAY, w, h, rcd));
996            }
997        }
998
999        public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
1000            // only post messages, we can't block here
1001            if ((mEventHandler != null) && (rcd != null)) {
1002                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
1003                        MSG_UNPLUG_DISPLAY, rcd));
1004            }
1005        }
1006
1007        public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
1008            // only post messages, we can't block here
1009            if ((mEventHandler != null) && (rcd != null)) {
1010                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
1011                        MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
1012            }
1013        }
1014    };
1015
1016    /**
1017     * @hide
1018     * Default value for the unique identifier
1019     */
1020    public final static int RCSE_ID_UNREGISTERED = -1;
1021    /**
1022     * Unique identifier of the RemoteControlStackEntry in AudioService with which
1023     * this RemoteControlClient is associated.
1024     */
1025    private int mRcseId = RCSE_ID_UNREGISTERED;
1026    /**
1027     * @hide
1028     * To be only used by AudioManager after it has received the unique id from
1029     * IAudioService.registerRemoteControlClient()
1030     * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
1031     *              this RemoteControlClient is associated.
1032     */
1033    public void setRcseId(int id) {
1034        mRcseId = id;
1035    }
1036
1037    /**
1038     * @hide
1039     */
1040    public int getRcseId() {
1041        return mRcseId;
1042    }
1043
1044    private EventHandler mEventHandler;
1045    private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
1046    private final static int MSG_REQUEST_METADATA = 2;
1047    private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
1048    private final static int MSG_REQUEST_ARTWORK = 4;
1049    private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
1050    private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
1051    private final static int MSG_PLUG_DISPLAY = 7;
1052    private final static int MSG_UNPLUG_DISPLAY = 8;
1053    private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9;
1054
1055    private class EventHandler extends Handler {
1056        public EventHandler(RemoteControlClient rcc, Looper looper) {
1057            super(looper);
1058        }
1059
1060        @Override
1061        public void handleMessage(Message msg) {
1062            switch(msg.what) {
1063                case MSG_REQUEST_PLAYBACK_STATE:
1064                    synchronized (mCacheLock) {
1065                        sendPlaybackState_syncCacheLock();
1066                    }
1067                    break;
1068                case MSG_REQUEST_METADATA:
1069                    synchronized (mCacheLock) {
1070                        sendMetadata_syncCacheLock();
1071                    }
1072                    break;
1073                case MSG_REQUEST_TRANSPORTCONTROL:
1074                    synchronized (mCacheLock) {
1075                        sendTransportControlFlags_syncCacheLock();
1076                    }
1077                    break;
1078                case MSG_REQUEST_ARTWORK:
1079                    synchronized (mCacheLock) {
1080                        sendArtwork_syncCacheLock();
1081                    }
1082                    break;
1083                case MSG_NEW_INTERNAL_CLIENT_GEN:
1084                    onNewInternalClientGen(msg.arg1);
1085                    break;
1086                case MSG_NEW_CURRENT_CLIENT_GEN:
1087                    onNewCurrentClientGen(msg.arg1);
1088                    break;
1089                case MSG_PLUG_DISPLAY:
1090                    onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
1091                    break;
1092                case MSG_UNPLUG_DISPLAY:
1093                    onUnplugDisplay((IRemoteControlDisplay)msg.obj);
1094                    break;
1095                case MSG_UPDATE_DISPLAY_ARTWORK_SIZE:
1096                    onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
1097                    break;
1098                default:
1099                    Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
1100            }
1101        }
1102    }
1103
1104    //===========================================================
1105    // Communication with the IRemoteControlDisplay (the displays known to the system)
1106
1107    private void sendPlaybackState_syncCacheLock() {
1108        if (mCurrentClientGenId == mInternalClientGenId) {
1109            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1110            while (displayIterator.hasNext()) {
1111                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1112                try {
1113                    di.mRcDisplay.setPlaybackState(mInternalClientGenId,
1114                            mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
1115                            mPlaybackSpeed);
1116                } catch (RemoteException e) {
1117                    Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
1118                    displayIterator.remove();
1119                }
1120            }
1121        }
1122    }
1123
1124    private void sendMetadata_syncCacheLock() {
1125        if (mCurrentClientGenId == mInternalClientGenId) {
1126            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1127            while (displayIterator.hasNext()) {
1128                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1129                try {
1130                    di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
1131                } catch (RemoteException e) {
1132                    Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
1133                    displayIterator.remove();
1134                }
1135            }
1136        }
1137    }
1138
1139    private void sendTransportControlFlags_syncCacheLock() {
1140        if (mCurrentClientGenId == mInternalClientGenId) {
1141            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1142            while (displayIterator.hasNext()) {
1143                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1144                try {
1145                    di.mRcDisplay.setTransportControlFlags(mInternalClientGenId,
1146                            mTransportControlFlags);
1147                } catch (RemoteException e) {
1148                    Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
1149                            e);
1150                    displayIterator.remove();
1151                }
1152            }
1153        }
1154    }
1155
1156    private void sendArtwork_syncCacheLock() {
1157        // FIXME modify to cache all requested sizes?
1158        if (mCurrentClientGenId == mInternalClientGenId) {
1159            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1160            while (displayIterator.hasNext()) {
1161                if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
1162                    displayIterator.remove();
1163                }
1164            }
1165        }
1166    }
1167
1168    /**
1169     * Send artwork to an IRemoteControlDisplay.
1170     * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its
1171     *    dimension requirements.
1172     * @return false if there was an error communicating with the IRemoteControlDisplay.
1173     */
1174    private boolean sendArtworkToDisplay(DisplayInfoForClient di) {
1175        if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
1176            Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
1177                    di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
1178            try {
1179                di.mRcDisplay.setArtwork(mInternalClientGenId, artwork);
1180            } catch (RemoteException e) {
1181                Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e);
1182                return false;
1183            }
1184        }
1185        return true;
1186    }
1187
1188    private void sendMetadataWithArtwork_syncCacheLock() {
1189        // FIXME modify to cache all requested sizes?
1190        if (mCurrentClientGenId == mInternalClientGenId) {
1191            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1192            while (displayIterator.hasNext()) {
1193                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1194                try {
1195                    if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
1196                        Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
1197                                di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
1198                        di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
1199                    } else {
1200                        di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
1201                    }
1202                } catch (RemoteException e) {
1203                    Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
1204                    displayIterator.remove();
1205                }
1206            }
1207        }
1208    }
1209
1210    //===========================================================
1211    // Communication with AudioService
1212
1213    private static IAudioService sService;
1214
1215    private static IAudioService getService()
1216    {
1217        if (sService != null) {
1218            return sService;
1219        }
1220        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
1221        sService = IAudioService.Stub.asInterface(b);
1222        return sService;
1223    }
1224
1225    private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
1226        if (mRcseId == RCSE_ID_UNREGISTERED) {
1227            return;
1228        }
1229        //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
1230        IAudioService service = getService();
1231        try {
1232            service.setPlaybackInfoForRcc(mRcseId, what, value);
1233        } catch (RemoteException e) {
1234            Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e);
1235        }
1236    }
1237
1238    private void sendAudioServiceNewPlaybackState_syncCacheLock() {
1239        if (mRcseId == RCSE_ID_UNREGISTERED) {
1240            return;
1241        }
1242        IAudioService service = getService();
1243        try {
1244            service.setPlaybackStateForRcc(mRcseId,
1245                    mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed);
1246        } catch (RemoteException e) {
1247            Log.e(TAG, "Dead object in setPlaybackStateForRcc", e);
1248        }
1249    }
1250
1251    //===========================================================
1252    // Message handlers
1253
1254    private void onNewInternalClientGen(int clientGeneration) {
1255        synchronized (mCacheLock) {
1256            // this remote control client is told it is the "focused" one:
1257            // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
1258            mInternalClientGenId = clientGeneration;
1259        }
1260    }
1261
1262    private void onNewCurrentClientGen(int clientGeneration) {
1263        synchronized (mCacheLock) {
1264            mCurrentClientGenId = clientGeneration;
1265        }
1266    }
1267
1268    /** pre-condition rcd != null */
1269    private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) {
1270        synchronized(mCacheLock) {
1271            // do we have this display already?
1272            boolean displayKnown = false;
1273            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1274            while (displayIterator.hasNext() && !displayKnown) {
1275                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1276                displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
1277                if (displayKnown) {
1278                    // this display was known but the change in artwork size will cause the
1279                    // artwork to be refreshed
1280                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
1281                        di.mArtworkExpectedWidth = w;
1282                        di.mArtworkExpectedHeight = h;
1283                        if (!sendArtworkToDisplay(di)) {
1284                            displayIterator.remove();
1285                        }
1286                    }
1287                }
1288            }
1289            if (!displayKnown) {
1290                mRcDisplays.add(new DisplayInfoForClient(rcd, w, h));
1291            }
1292        }
1293    }
1294
1295    /** pre-condition rcd != null */
1296    private void onUnplugDisplay(IRemoteControlDisplay rcd) {
1297        synchronized(mCacheLock) {
1298            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1299            while (displayIterator.hasNext()) {
1300                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1301                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1302                    displayIterator.remove();
1303                    return;
1304                }
1305            }
1306        }
1307    }
1308
1309    /** pre-condition rcd != null */
1310    private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) {
1311        synchronized(mCacheLock) {
1312            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
1313            while (displayIterator.hasNext()) {
1314                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
1315                if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
1316                        ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
1317                    di.mArtworkExpectedWidth = w;
1318                    di.mArtworkExpectedHeight = h;
1319                    if (!sendArtworkToDisplay(di)) {
1320                        displayIterator.remove();
1321                    }
1322                    break;
1323                }
1324            }
1325        }
1326    }
1327
1328    //===========================================================
1329    // Internal utilities
1330
1331    /**
1332     * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
1333     * If the bitmap fits, then do nothing and return the original.
1334     *
1335     * @param bitmap
1336     * @param maxWidth
1337     * @param maxHeight
1338     * @return
1339     */
1340
1341    private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
1342        if (bitmap != null) {
1343            final int width = bitmap.getWidth();
1344            final int height = bitmap.getHeight();
1345            if (width > maxWidth || height > maxHeight) {
1346                float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
1347                int newWidth = Math.round(scale * width);
1348                int newHeight = Math.round(scale * height);
1349                Bitmap.Config newConfig = bitmap.getConfig();
1350                if (newConfig == null) {
1351                    newConfig = Bitmap.Config.ARGB_8888;
1352                }
1353                Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
1354                Canvas canvas = new Canvas(outBitmap);
1355                Paint paint = new Paint();
1356                paint.setAntiAlias(true);
1357                paint.setFilterBitmap(true);
1358                canvas.drawBitmap(bitmap, null,
1359                        new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
1360                bitmap = outBitmap;
1361            }
1362        }
1363        return bitmap;
1364    }
1365
1366    /**
1367     *  Fast routine to go through an array of allowed keys and return whether the key is part
1368     *  of that array
1369     * @param key the key value
1370     * @param validKeys the array of valid keys for a given type
1371     * @return true if the key is part of the array, false otherwise
1372     */
1373    private static boolean validTypeForKey(int key, int[] validKeys) {
1374        try {
1375            for (int i = 0 ; ; i++) {
1376                if (key == validKeys[i]) {
1377                    return true;
1378                }
1379            }
1380        } catch (ArrayIndexOutOfBoundsException e) {
1381            return false;
1382        }
1383    }
1384}
1385