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