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