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