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