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