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