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