RemoteControlClient.java revision 466ade5ad66e7bfb1814d5e5ac76a17f8a0bcd3a
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.content.ComponentName;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Paint;
23import android.graphics.RectF;
24import android.media.MediaMetadataRetriever;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.Log;
31
32import java.lang.IllegalArgumentException;
33
34/**
35 * RemoteControlClient enables exposing information meant to be consumed by remote controls
36 * capable of displaying metadata, artwork and media transport control buttons.
37 * A remote control client object is associated with a media button event receiver. This
38 * event receiver must have been previously registered with
39 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
40 * RemoteControlClient can be registered through
41 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
42 */
43public class RemoteControlClient
44{
45    private final static String TAG = "RemoteControlClient";
46
47    /**
48     * Playback state of a RemoteControlClient which is stopped.
49     *
50     * @see #setPlaybackState(int)
51     */
52    public final static int PLAYSTATE_STOPPED            = 1;
53    /**
54     * Playback state of a RemoteControlClient which is paused.
55     *
56     * @see #setPlaybackState(int)
57     */
58    public final static int PLAYSTATE_PAUSED             = 2;
59    /**
60     * Playback state of a RemoteControlClient which is playing media.
61     *
62     * @see #setPlaybackState(int)
63     */
64    public final static int PLAYSTATE_PLAYING            = 3;
65    /**
66     * Playback state of a RemoteControlClient which is fast forwarding in the media
67     *    it is currently playing.
68     *
69     * @see #setPlaybackState(int)
70     */
71    public final static int PLAYSTATE_FAST_FORWARDING    = 4;
72    /**
73     * Playback state of a RemoteControlClient which is fast rewinding in the media
74     *    it is currently playing.
75     *
76     * @see #setPlaybackState(int)
77     */
78    public final static int PLAYSTATE_REWINDING          = 5;
79    /**
80     * Playback state of a RemoteControlClient which is skipping to the next
81     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
82     *
83     * @see #setPlaybackState(int)
84     */
85    public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
86    /**
87     * Playback state of a RemoteControlClient which is skipping back to the previous
88     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
89     *
90     * @see #setPlaybackState(int)
91     */
92    public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
93    /**
94     * Playback state of a RemoteControlClient which is buffering data to play before it can
95     *    start or resume playback.
96     *
97     * @see #setPlaybackState(int)
98     */
99    public final static int PLAYSTATE_BUFFERING          = 8;
100    /**
101     * Playback state of a RemoteControlClient which cannot perform any playback related
102     *    operation because of an internal error. Examples of such situations are no network
103     *    connectivity when attempting to stream data from a server, or expired user credentials
104     *    when trying to play subscription-based content.
105     *
106     * @see #setPlaybackState(int)
107     */
108    public final static int PLAYSTATE_ERROR              = 9;
109    /**
110     * @hide
111     * The value of a playback state when none has been declared.
112     * Intentionally hidden as an application shouldn't set such a playback state value.
113     */
114    public final static int PLAYSTATE_NONE               = 0;
115
116    /**
117     * Flag indicating a RemoteControlClient makes use of the "previous" media key.
118     *
119     * @see #setTransportControlFlags(int)
120     * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
121     */
122    public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
123    /**
124     * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
125     *
126     * @see #setTransportControlFlags(int)
127     * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
128     */
129    public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
130    /**
131     * Flag indicating a RemoteControlClient makes use of the "play" media key.
132     *
133     * @see #setTransportControlFlags(int)
134     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
135     */
136    public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
137    /**
138     * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
139     *
140     * @see #setTransportControlFlags(int)
141     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
142     */
143    public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
144    /**
145     * Flag indicating a RemoteControlClient makes use of the "pause" media key.
146     *
147     * @see #setTransportControlFlags(int)
148     * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
149     */
150    public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
151    /**
152     * Flag indicating a RemoteControlClient makes use of the "stop" media key.
153     *
154     * @see #setTransportControlFlags(int)
155     * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
156     */
157    public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
158    /**
159     * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
160     *
161     * @see #setTransportControlFlags(int)
162     * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
163     */
164    public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
165    /**
166     * Flag indicating a RemoteControlClient makes use of the "next" media key.
167     *
168     * @see #setTransportControlFlags(int)
169     * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
170     */
171    public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
172
173    /**
174     * @hide
175     * The flags for when no media keys are declared supported.
176     * Intentionally hidden as an application shouldn't set the transport control flags
177     *     to this value.
178     */
179    public final static int FLAGS_KEY_MEDIA_NONE = 0;
180
181    /**
182     * @hide
183     * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
184     */
185    public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
186    /**
187     * @hide
188     * Flag used to signal that the transport control buttons supported by the
189     *     RemoteControlClient are requested.
190     * This can for instance happen when playback is at the end of a playlist, and the "next"
191     * operation is not supported anymore.
192     */
193    public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
194    /**
195     * @hide
196     * Flag used to signal that the playback state of the RemoteControlClient is requested.
197     */
198    public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
199    /**
200     * @hide
201     * Flag used to signal that the album art for the RemoteControlClient is requested.
202     */
203    public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
204
205    /**
206     * Class constructor.
207     * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
208     *     been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
209     *     before this new RemoteControlClient can itself be registered with
210     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
211     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
212     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
213     */
214    public RemoteControlClient(ComponentName mediaButtonEventReceiver) {
215        mRcEventReceiver = mediaButtonEventReceiver;
216
217        Looper looper;
218        if ((looper = Looper.myLooper()) != null) {
219            mEventHandler = new EventHandler(this, looper);
220        } else if ((looper = Looper.getMainLooper()) != null) {
221            mEventHandler = new EventHandler(this, looper);
222        } else {
223            mEventHandler = null;
224            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
225        }
226    }
227
228    /**
229     * Class constructor for a remote control client whose internal event handling
230     * happens on a user-provided Looper.
231     * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
232     *     been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
233     *     before this new RemoteControlClient can itself be registered with
234     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
235     * @param looper The Looper running the event loop.
236     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
237     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
238     */
239    public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) {
240        mRcEventReceiver = mediaButtonEventReceiver;
241
242        mEventHandler = new EventHandler(this, looper);
243    }
244
245    private static final int[] METADATA_KEYS_TYPE_STRING = {
246        MediaMetadataRetriever.METADATA_KEY_ALBUM,
247        MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
248        MediaMetadataRetriever.METADATA_KEY_TITLE,
249        MediaMetadataRetriever.METADATA_KEY_ARTIST,
250        MediaMetadataRetriever.METADATA_KEY_AUTHOR,
251        MediaMetadataRetriever.METADATA_KEY_COMPILATION,
252        MediaMetadataRetriever.METADATA_KEY_COMPOSER,
253        MediaMetadataRetriever.METADATA_KEY_DATE,
254        MediaMetadataRetriever.METADATA_KEY_GENRE,
255        MediaMetadataRetriever.METADATA_KEY_TITLE,
256        MediaMetadataRetriever.METADATA_KEY_WRITER };
257    private static final int[] METADATA_KEYS_TYPE_LONG = {
258        MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
259        MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
260        MediaMetadataRetriever.METADATA_KEY_DURATION };
261
262    /**
263     * Class used to modify metadata in a {@link RemoteControlClient} object.
264     * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
265     * on which you set the metadata for the RemoteControlClient instance. Once all the information
266     * has been set, use {@link #apply()} to make it the new metadata that should be displayed
267     * for the associated client. Once the metadata has been "applied", you cannot reuse this
268     * instance of the MetadataEditor.
269     */
270    public class MetadataEditor {
271        /**
272         * @hide
273         */
274        protected boolean mMetadataChanged;
275        /**
276         * @hide
277         */
278        protected boolean mArtworkChanged;
279        /**
280         * @hide
281         */
282        protected Bitmap mEditorArtwork;
283        /**
284         * @hide
285         */
286        protected Bundle mEditorMetadata;
287        private boolean mApplied = false;
288
289        // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
290        private MetadataEditor() { }
291        /**
292         * @hide
293         */
294        public Object clone() throws CloneNotSupportedException {
295            throw new CloneNotSupportedException();
296        }
297
298        /**
299         * The metadata key for the content artwork / album art.
300         */
301        public final static int BITMAP_KEY_ARTWORK = 100;
302        /**
303         * @hide
304         * TODO(jmtrivi) have lockscreen and music move to the new key name
305         */
306        public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
307
308        /**
309         * Adds textual information to be displayed.
310         * Note that none of the information added after {@link #apply()} has been called,
311         * will be displayed.
312         * @param key The identifier of a the metadata field to set. Valid values are
313         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
314         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
315         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
316         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
317         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
318         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
319         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
320         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
321         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
322         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
323         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
324         * @param value The text for the given key, or {@code null} to signify there is no valid
325         *      information for the field.
326         * @return Returns a reference to the same MetadataEditor object, so you can chain put
327         *      calls together.
328         */
329        public synchronized MetadataEditor putString(int key, String value)
330                throws IllegalArgumentException {
331            if (mApplied) {
332                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
333                return this;
334            }
335            if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
336                throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
337            }
338            mEditorMetadata.putString(String.valueOf(key), value);
339            mMetadataChanged = true;
340            return this;
341        }
342
343        /**
344         * Adds numerical information to be displayed.
345         * Note that none of the information added after {@link #apply()} has been called,
346         * will be displayed.
347         * @param key the identifier of a the metadata field to set. Valid values are
348         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
349         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
350         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
351         *      expressed in milliseconds),
352         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
353         * @param value The long value for the given key
354         * @return Returns a reference to the same MetadataEditor object, so you can chain put
355         *      calls together.
356         * @throws IllegalArgumentException
357         */
358        public synchronized MetadataEditor putLong(int key, long value)
359                throws IllegalArgumentException {
360            if (mApplied) {
361                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
362                return this;
363            }
364            if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
365                throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
366            }
367            mEditorMetadata.putLong(String.valueOf(key), value);
368            mMetadataChanged = true;
369            return this;
370        }
371
372        /**
373         * Sets the album / artwork picture to be displayed on the remote control.
374         * @param key the identifier of the bitmap to set. The only valid value is
375         *      {@link #BITMAP_KEY_ARTWORK}
376         * @param bitmap The bitmap for the artwork, or null if there isn't any.
377         * @return Returns a reference to the same MetadataEditor object, so you can chain put
378         *      calls together.
379         * @throws IllegalArgumentException
380         * @see android.graphics.Bitmap
381         */
382        public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
383                throws IllegalArgumentException {
384            if (mApplied) {
385                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
386                return this;
387            }
388            if (key != BITMAP_KEY_ARTWORK) {
389                throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
390            }
391            if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) {
392                mEditorArtwork = scaleBitmapIfTooBig(bitmap,
393                        mArtworkExpectedWidth, mArtworkExpectedHeight);
394            } else {
395                // no valid resize dimensions, store as is
396                mEditorArtwork = bitmap;
397            }
398            mArtworkChanged = true;
399            return this;
400        }
401
402        /**
403         * Clears all the metadata that has been set since the MetadataEditor instance was
404         *     created with {@link RemoteControlClient#editMetadata(boolean)}.
405         */
406        public synchronized void clear() {
407            if (mApplied) {
408                Log.e(TAG, "Can't clear a previously applied MetadataEditor");
409                return;
410            }
411            mEditorMetadata.clear();
412            mEditorArtwork = null;
413        }
414
415        /**
416         * Associates all the metadata that has been set since the MetadataEditor instance was
417         *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
418         *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
419         *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
420         */
421        public synchronized void apply() {
422            if (mApplied) {
423                Log.e(TAG, "Can't apply a previously applied MetadataEditor");
424                return;
425            }
426            synchronized(mCacheLock) {
427                // assign the edited data
428                mMetadata = new Bundle(mEditorMetadata);
429                mArtwork = mEditorArtwork;
430                if (mMetadataChanged & mArtworkChanged) {
431                    // send to remote control display if conditions are met
432                    sendMetadataWithArtwork_syncCacheLock();
433                } else if (mMetadataChanged) {
434                    // send to remote control display if conditions are met
435                    sendMetadata_syncCacheLock();
436                } else if (mArtworkChanged) {
437                    // send to remote control display if conditions are met
438                    sendArtwork_syncCacheLock();
439                }
440                mApplied = true;
441            }
442        }
443    }
444
445    /**
446     * Creates a {@link MetadataEditor}.
447     * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
448     *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
449     * @return a new MetadataEditor instance.
450     */
451    public MetadataEditor editMetadata(boolean startEmpty) {
452        MetadataEditor editor = new MetadataEditor();
453        if (startEmpty) {
454            editor.mEditorMetadata = new Bundle();
455            editor.mEditorArtwork = null;
456            editor.mMetadataChanged = true;
457            editor.mArtworkChanged = true;
458        } else {
459            editor.mEditorMetadata = new Bundle(mMetadata);
460            editor.mEditorArtwork = mArtwork;
461            editor.mMetadataChanged = false;
462            editor.mArtworkChanged = false;
463        }
464        return editor;
465    }
466
467    /**
468     * Sets the current playback state.
469     * @param state The current playback state, one of the following values:
470     *       {@link #PLAYSTATE_STOPPED},
471     *       {@link #PLAYSTATE_PAUSED},
472     *       {@link #PLAYSTATE_PLAYING},
473     *       {@link #PLAYSTATE_FAST_FORWARDING},
474     *       {@link #PLAYSTATE_REWINDING},
475     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
476     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
477     *       {@link #PLAYSTATE_BUFFERING},
478     *       {@link #PLAYSTATE_ERROR}.
479     */
480    public void setPlaybackState(int state) {
481        synchronized(mCacheLock) {
482            // store locally
483            mPlaybackState = state;
484
485            // send to remote control display if conditions are met
486            sendPlaybackState_syncCacheLock();
487        }
488    }
489
490    /**
491     * Sets the flags for the media transport control buttons that this client supports.
492     * @param transportControlFlags A combination of the following flags:
493     *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
494     *      {@link #FLAG_KEY_MEDIA_REWIND},
495     *      {@link #FLAG_KEY_MEDIA_PLAY},
496     *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
497     *      {@link #FLAG_KEY_MEDIA_PAUSE},
498     *      {@link #FLAG_KEY_MEDIA_STOP},
499     *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
500     *      {@link #FLAG_KEY_MEDIA_NEXT}
501     */
502    public void setTransportControlFlags(int transportControlFlags) {
503        synchronized(mCacheLock) {
504            // store locally
505            mTransportControlFlags = transportControlFlags;
506
507            // send to remote control display if conditions are met
508            sendTransportControlFlags_syncCacheLock();
509        }
510    }
511
512    /**
513     * Lock for all cached data
514     */
515    private final Object mCacheLock = new Object();
516    /**
517     * Cache for the playback state.
518     * Access synchronized on mCacheLock
519     */
520    private int mPlaybackState = PLAYSTATE_NONE;
521    /**
522     * Cache for the artwork bitmap.
523     * Access synchronized on mCacheLock
524     * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
525     * accessed to be resized, in which case a copy will be made. This would add overhead in
526     * Bundle operations.
527     */
528    private Bitmap mArtwork;
529    private final int ARTWORK_DEFAULT_SIZE = 256;
530    private final int ARTWORK_INVALID_SIZE = -1;
531    private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
532    private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
533    /**
534     * Cache for the transport control mask.
535     * Access synchronized on mCacheLock
536     */
537    private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
538    /**
539     * Cache for the metadata strings.
540     * Access synchronized on mCacheLock
541     */
542    private Bundle mMetadata = new Bundle();
543
544    /**
545     * The current remote control client generation ID across the system
546     */
547    private int mCurrentClientGenId = -1;
548    /**
549     * The remote control client generation ID, the last time it was told it was the current RC.
550     * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
551     * client is the "focused" one, and that whenever this client's info is updated, it needs to
552     * send it to the known IRemoteControlDisplay interfaces.
553     */
554    private int mInternalClientGenId = -2;
555
556    /**
557     * The media button event receiver associated with this remote control client
558     */
559    private final ComponentName mRcEventReceiver;
560
561    /**
562     * The remote control display to which this client will send information.
563     * NOTE: Only one IRemoteControlDisplay supported in this implementation
564     */
565    private IRemoteControlDisplay mRcDisplay;
566
567    /**
568     * @hide
569     * Accessor to media button event receiver
570     */
571    public ComponentName getRcEventReceiver() {
572        return mRcEventReceiver;
573    }
574    /**
575     * @hide
576     * Accessor to IRemoteControlClient
577     */
578    public IRemoteControlClient getIRemoteControlClient() {
579        return mIRCC;
580    }
581
582    /**
583     * The IRemoteControlClient implementation
584     */
585    private IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
586
587        public void onInformationRequested(int clientGeneration, int infoFlags,
588                int artWidth, int artHeight) {
589            // only post messages, we can't block here
590            if (mEventHandler != null) {
591                // signal new client
592                mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
593                mEventHandler.dispatchMessage(
594                        mEventHandler.obtainMessage(
595                                MSG_NEW_INTERNAL_CLIENT_GEN,
596                                artWidth, artHeight,
597                                new Integer(clientGeneration)));
598                // send the information
599                mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
600                mEventHandler.removeMessages(MSG_REQUEST_METADATA);
601                mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
602                mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
603                mEventHandler.dispatchMessage(
604                        mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
605                mEventHandler.dispatchMessage(
606                        mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
607                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
608                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
609            }
610        }
611
612        public void setCurrentClientGenerationId(int clientGeneration) {
613            // only post messages, we can't block here
614            if (mEventHandler != null) {
615                mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
616                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
617                        MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
618            }
619        }
620
621        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
622            // only post messages, we can't block here
623            if (mEventHandler != null) {
624                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
625                        MSG_PLUG_DISPLAY, rcd));
626            }
627        }
628
629        public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
630            // only post messages, we can't block here
631            if (mEventHandler != null) {
632                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
633                        MSG_UNPLUG_DISPLAY, rcd));
634            }
635        }
636    };
637
638    private EventHandler mEventHandler;
639    private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
640    private final static int MSG_REQUEST_METADATA = 2;
641    private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
642    private final static int MSG_REQUEST_ARTWORK = 4;
643    private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
644    private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
645    private final static int MSG_PLUG_DISPLAY = 7;
646    private final static int MSG_UNPLUG_DISPLAY = 8;
647
648    private class EventHandler extends Handler {
649        public EventHandler(RemoteControlClient rcc, Looper looper) {
650            super(looper);
651        }
652
653        @Override
654        public void handleMessage(Message msg) {
655            switch(msg.what) {
656                case MSG_REQUEST_PLAYBACK_STATE:
657                    synchronized (mCacheLock) {
658                        sendPlaybackState_syncCacheLock();
659                    }
660                    break;
661                case MSG_REQUEST_METADATA:
662                    synchronized (mCacheLock) {
663                        sendMetadata_syncCacheLock();
664                    }
665                    break;
666                case MSG_REQUEST_TRANSPORTCONTROL:
667                    synchronized (mCacheLock) {
668                        sendTransportControlFlags_syncCacheLock();
669                    }
670                    break;
671                case MSG_REQUEST_ARTWORK:
672                    synchronized (mCacheLock) {
673                        sendArtwork_syncCacheLock();
674                    }
675                    break;
676                case MSG_NEW_INTERNAL_CLIENT_GEN:
677                    onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
678                    break;
679                case MSG_NEW_CURRENT_CLIENT_GEN:
680                    onNewCurrentClientGen(msg.arg1);
681                    break;
682                case MSG_PLUG_DISPLAY:
683                    onPlugDisplay((IRemoteControlDisplay)msg.obj);
684                    break;
685                case MSG_UNPLUG_DISPLAY:
686                    onUnplugDisplay((IRemoteControlDisplay)msg.obj);
687                    break;
688                default:
689                    Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
690            }
691        }
692    }
693
694    private void detachFromDisplay_syncCacheLock() {
695        mRcDisplay = null;
696        mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
697        mArtworkExpectedHeight = ARTWORK_INVALID_SIZE;
698    }
699
700    private void sendPlaybackState_syncCacheLock() {
701        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
702            try {
703                mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState);
704            } catch (RemoteException e) {
705                Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
706                detachFromDisplay_syncCacheLock();
707            }
708        }
709    }
710
711    private void sendMetadata_syncCacheLock() {
712        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
713            try {
714                mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
715            } catch (RemoteException e) {
716                Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
717                detachFromDisplay_syncCacheLock();
718            }
719        }
720    }
721
722    private void sendTransportControlFlags_syncCacheLock() {
723        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
724            try {
725                mRcDisplay.setTransportControlFlags(mInternalClientGenId,
726                        mTransportControlFlags);
727            } catch (RemoteException e) {
728                Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
729                detachFromDisplay_syncCacheLock();
730            }
731        }
732    }
733
734    private void sendArtwork_syncCacheLock() {
735        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
736            // even though we have already scaled in setArtwork(), when this client needs to
737            // send the bitmap, there might be newer and smaller expected dimensions, so we have
738            // to check again.
739            mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
740            try {
741                mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
742            } catch (RemoteException e) {
743                Log.e(TAG, "Error in sendArtwork(), dead display "+e);
744                detachFromDisplay_syncCacheLock();
745            }
746        }
747    }
748
749    private void sendMetadataWithArtwork_syncCacheLock() {
750        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
751            // even though we have already scaled in setArtwork(), when this client needs to
752            // send the bitmap, there might be newer and smaller expected dimensions, so we have
753            // to check again.
754            mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
755            try {
756                mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork);
757            } catch (RemoteException e) {
758                Log.e(TAG, "Error in setAllMetadata(), dead display "+e);
759                detachFromDisplay_syncCacheLock();
760            }
761        }
762    }
763
764    private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
765        synchronized (mCacheLock) {
766            // this remote control client is told it is the "focused" one:
767            // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
768            mInternalClientGenId = clientGeneration.intValue();
769            if (artWidth > 0) {
770                mArtworkExpectedWidth = artWidth;
771                mArtworkExpectedHeight = artHeight;
772            }
773        }
774    }
775
776    private void onNewCurrentClientGen(int clientGeneration) {
777        synchronized (mCacheLock) {
778            mCurrentClientGenId = clientGeneration;
779        }
780    }
781
782    private void onPlugDisplay(IRemoteControlDisplay rcd) {
783        synchronized(mCacheLock) {
784            mRcDisplay = rcd;
785        }
786    }
787
788    private void onUnplugDisplay(IRemoteControlDisplay rcd) {
789        synchronized(mCacheLock) {
790            if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) {
791                mRcDisplay = null;
792                mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
793                mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
794            }
795        }
796    }
797
798    /**
799     * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
800     * If the bitmap fits, then do nothing and return the original.
801     *
802     * @param bitmap
803     * @param maxWidth
804     * @param maxHeight
805     * @return
806     */
807
808    private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
809        if (bitmap != null) {
810            final int width = bitmap.getWidth();
811            final int height = bitmap.getHeight();
812            if (width > maxWidth || height > maxHeight) {
813                float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
814                int newWidth = Math.round(scale * width);
815                int newHeight = Math.round(scale * height);
816                Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.getConfig());
817                Canvas canvas = new Canvas(outBitmap);
818                Paint paint = new Paint();
819                paint.setAntiAlias(true);
820                paint.setFilterBitmap(true);
821                canvas.drawBitmap(bitmap, null,
822                        new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
823                bitmap = outBitmap;
824            }
825        }
826        return bitmap;
827    }
828
829    /**
830     *  Fast routine to go through an array of allowed keys and return whether the key is part
831     *  of that array
832     * @param key the key value
833     * @param validKeys the array of valid keys for a given type
834     * @return true if the key is part of the array, false otherwise
835     */
836    private static boolean validTypeForKey(int key, int[] validKeys) {
837        try {
838            for (int i = 0 ; ; i++) {
839                if (key == validKeys[i]) {
840                    return true;
841                }
842            }
843        } catch (ArrayIndexOutOfBoundsException e) {
844            return false;
845        }
846    }
847}
848