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