• 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.Context;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.RectF;
27 import android.media.MediaMetadataRetriever;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.SystemClock;
36 import android.util.Log;
37 
38 import java.lang.IllegalArgumentException;
39 
40 /**
41  * RemoteControlClient enables exposing information meant to be consumed by remote controls
42  * capable of displaying metadata, artwork and media transport control buttons.
43  *
44  * <p>A remote control client object is associated with a media button event receiver. This
45  * event receiver must have been previously registered with
46  * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
47  * RemoteControlClient can be registered through
48  * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
49  *
50  * <p>Here is an example of creating a RemoteControlClient instance after registering a media
51  * button event receiver:
52  * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
53  * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
54  * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
55  * // build the PendingIntent for the remote control client
56  * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
57  * mediaButtonIntent.setComponent(myEventReceiver);
58  * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
59  * // create and register the remote control client
60  * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
61  * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre>
62  */
63 public class RemoteControlClient
64 {
65     private final static String TAG = "RemoteControlClient";
66 
67     /**
68      * Playback state of a RemoteControlClient which is stopped.
69      *
70      * @see #setPlaybackState(int)
71      */
72     public final static int PLAYSTATE_STOPPED            = 1;
73     /**
74      * Playback state of a RemoteControlClient which is paused.
75      *
76      * @see #setPlaybackState(int)
77      */
78     public final static int PLAYSTATE_PAUSED             = 2;
79     /**
80      * Playback state of a RemoteControlClient which is playing media.
81      *
82      * @see #setPlaybackState(int)
83      */
84     public final static int PLAYSTATE_PLAYING            = 3;
85     /**
86      * Playback state of a RemoteControlClient which is fast forwarding in the media
87      *    it is currently playing.
88      *
89      * @see #setPlaybackState(int)
90      */
91     public final static int PLAYSTATE_FAST_FORWARDING    = 4;
92     /**
93      * Playback state of a RemoteControlClient which is fast rewinding in the media
94      *    it is currently playing.
95      *
96      * @see #setPlaybackState(int)
97      */
98     public final static int PLAYSTATE_REWINDING          = 5;
99     /**
100      * Playback state of a RemoteControlClient which is skipping to the next
101      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
102      *
103      * @see #setPlaybackState(int)
104      */
105     public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
106     /**
107      * Playback state of a RemoteControlClient which is skipping back to the previous
108      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
109      *
110      * @see #setPlaybackState(int)
111      */
112     public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
113     /**
114      * Playback state of a RemoteControlClient which is buffering data to play before it can
115      *    start or resume playback.
116      *
117      * @see #setPlaybackState(int)
118      */
119     public final static int PLAYSTATE_BUFFERING          = 8;
120     /**
121      * Playback state of a RemoteControlClient which cannot perform any playback related
122      *    operation because of an internal error. Examples of such situations are no network
123      *    connectivity when attempting to stream data from a server, or expired user credentials
124      *    when trying to play subscription-based content.
125      *
126      * @see #setPlaybackState(int)
127      */
128     public final static int PLAYSTATE_ERROR              = 9;
129     /**
130      * @hide
131      * The value of a playback state when none has been declared.
132      * Intentionally hidden as an application shouldn't set such a playback state value.
133      */
134     public final static int PLAYSTATE_NONE               = 0;
135 
136     /**
137      * @hide
138      * The default playback type, "local", indicating the presentation of the media is happening on
139      * the same device (e.g. a phone, a tablet) as where it is controlled from.
140      */
141     public final static int PLAYBACK_TYPE_LOCAL = 0;
142     /**
143      * @hide
144      * A playback type indicating the presentation of the media is happening on
145      * a different device (i.e. the remote device) than where it is controlled from.
146      */
147     public final static int PLAYBACK_TYPE_REMOTE = 1;
148     private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
149     private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
150     /**
151      * @hide
152      * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
153      * from this object. An example of fixed playback volume is a remote player, playing over HDMI
154      * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
155      * source.
156      * @see #PLAYBACKINFO_VOLUME_HANDLING.
157      */
158     public final static int PLAYBACK_VOLUME_FIXED = 0;
159     /**
160      * @hide
161      * Playback information indicating the playback volume is variable and can be controlled from
162      * this object.
163      * @see #PLAYBACKINFO_VOLUME_HANDLING.
164      */
165     public final static int PLAYBACK_VOLUME_VARIABLE = 1;
166     /**
167      * @hide (to be un-hidden)
168      * The playback information value indicating the value of a given information type is invalid.
169      * @see #PLAYBACKINFO_VOLUME_HANDLING.
170      */
171     public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
172 
173     //==========================================
174     // Public keys for playback information
175     /**
176      * @hide
177      * Playback information that defines the type of playback associated with this
178      * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
179      */
180     public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
181     /**
182      * @hide
183      * Playback information that defines at what volume the playback associated with this
184      * RemoteControlClient is performed. This information is only used when the playback type is not
185      * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
186      */
187     public final static int PLAYBACKINFO_VOLUME = 2;
188     /**
189      * @hide
190      * Playback information that defines the maximum volume volume value that is supported
191      * by the playback associated with this RemoteControlClient. This information is only used
192      * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
193      */
194     public final static int PLAYBACKINFO_VOLUME_MAX = 3;
195     /**
196      * @hide
197      * Playback information that defines how volume is handled for the presentation of the media.
198      * @see #PLAYBACK_VOLUME_FIXED
199      * @see #PLAYBACK_VOLUME_VARIABLE
200      */
201     public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
202     /**
203      * @hide
204      * Playback information that defines over what stream type the media is presented.
205      */
206     public final static int PLAYBACKINFO_USES_STREAM = 5;
207 
208     //==========================================
209     // Private keys for playback information
210     /**
211      * @hide
212      * Used internally to relay playback state (set by the application with
213      * {@link #setPlaybackState(int)}) to AudioService
214      */
215     public final static int PLAYBACKINFO_PLAYSTATE = 255;
216 
217 
218     /**
219      * Flag indicating a RemoteControlClient makes use of the "previous" media key.
220      *
221      * @see #setTransportControlFlags(int)
222      * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
223      */
224     public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
225     /**
226      * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
227      *
228      * @see #setTransportControlFlags(int)
229      * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
230      */
231     public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
232     /**
233      * Flag indicating a RemoteControlClient makes use of the "play" media key.
234      *
235      * @see #setTransportControlFlags(int)
236      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
237      */
238     public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
239     /**
240      * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
241      *
242      * @see #setTransportControlFlags(int)
243      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
244      */
245     public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
246     /**
247      * Flag indicating a RemoteControlClient makes use of the "pause" media key.
248      *
249      * @see #setTransportControlFlags(int)
250      * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
251      */
252     public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
253     /**
254      * Flag indicating a RemoteControlClient makes use of the "stop" media key.
255      *
256      * @see #setTransportControlFlags(int)
257      * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
258      */
259     public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
260     /**
261      * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
262      *
263      * @see #setTransportControlFlags(int)
264      * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
265      */
266     public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
267     /**
268      * Flag indicating a RemoteControlClient makes use of the "next" media key.
269      *
270      * @see #setTransportControlFlags(int)
271      * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
272      */
273     public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
274 
275     /**
276      * @hide
277      * The flags for when no media keys are declared supported.
278      * Intentionally hidden as an application shouldn't set the transport control flags
279      *     to this value.
280      */
281     public final static int FLAGS_KEY_MEDIA_NONE = 0;
282 
283     /**
284      * @hide
285      * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
286      */
287     public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
288     /**
289      * @hide
290      * Flag used to signal that the transport control buttons supported by the
291      *     RemoteControlClient are requested.
292      * This can for instance happen when playback is at the end of a playlist, and the "next"
293      * operation is not supported anymore.
294      */
295     public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
296     /**
297      * @hide
298      * Flag used to signal that the playback state of the RemoteControlClient is requested.
299      */
300     public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
301     /**
302      * @hide
303      * Flag used to signal that the album art for the RemoteControlClient is requested.
304      */
305     public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
306 
307     /**
308      * Class constructor.
309      * @param mediaButtonIntent The intent that will be sent for the media button events sent
310      *     by remote controls.
311      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
312      *     action, and have a component that will handle the intent (set with
313      *     {@link Intent#setComponent(ComponentName)}) registered with
314      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
315      *     before this new RemoteControlClient can itself be registered with
316      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
317      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
318      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
319      */
RemoteControlClient(PendingIntent mediaButtonIntent)320     public RemoteControlClient(PendingIntent mediaButtonIntent) {
321         mRcMediaIntent = mediaButtonIntent;
322 
323         Looper looper;
324         if ((looper = Looper.myLooper()) != null) {
325             mEventHandler = new EventHandler(this, looper);
326         } else if ((looper = Looper.getMainLooper()) != null) {
327             mEventHandler = new EventHandler(this, looper);
328         } else {
329             mEventHandler = null;
330             Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
331         }
332     }
333 
334     /**
335      * Class constructor for a remote control client whose internal event handling
336      * happens on a user-provided Looper.
337      * @param mediaButtonIntent The intent that will be sent for the media button events sent
338      *     by remote controls.
339      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
340      *     action, and have a component that will handle the intent (set with
341      *     {@link Intent#setComponent(ComponentName)}) registered with
342      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
343      *     before this new RemoteControlClient can itself be registered with
344      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
345      * @param looper The Looper running the event loop.
346      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
347      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
348      */
RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper)349     public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
350         mRcMediaIntent = mediaButtonIntent;
351 
352         mEventHandler = new EventHandler(this, looper);
353     }
354 
355     private static final int[] METADATA_KEYS_TYPE_STRING = {
356         MediaMetadataRetriever.METADATA_KEY_ALBUM,
357         MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
358         MediaMetadataRetriever.METADATA_KEY_TITLE,
359         MediaMetadataRetriever.METADATA_KEY_ARTIST,
360         MediaMetadataRetriever.METADATA_KEY_AUTHOR,
361         MediaMetadataRetriever.METADATA_KEY_COMPILATION,
362         MediaMetadataRetriever.METADATA_KEY_COMPOSER,
363         MediaMetadataRetriever.METADATA_KEY_DATE,
364         MediaMetadataRetriever.METADATA_KEY_GENRE,
365         MediaMetadataRetriever.METADATA_KEY_TITLE,
366         MediaMetadataRetriever.METADATA_KEY_WRITER };
367     private static final int[] METADATA_KEYS_TYPE_LONG = {
368         MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
369         MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
370         MediaMetadataRetriever.METADATA_KEY_DURATION };
371 
372     /**
373      * Class used to modify metadata in a {@link RemoteControlClient} object.
374      * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
375      * on which you set the metadata for the RemoteControlClient instance. Once all the information
376      * has been set, use {@link #apply()} to make it the new metadata that should be displayed
377      * for the associated client. Once the metadata has been "applied", you cannot reuse this
378      * instance of the MetadataEditor.
379      */
380     public class MetadataEditor {
381         /**
382          * @hide
383          */
384         protected boolean mMetadataChanged;
385         /**
386          * @hide
387          */
388         protected boolean mArtworkChanged;
389         /**
390          * @hide
391          */
392         protected Bitmap mEditorArtwork;
393         /**
394          * @hide
395          */
396         protected Bundle mEditorMetadata;
397         private boolean mApplied = false;
398 
399         // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
MetadataEditor()400         private MetadataEditor() { }
401         /**
402          * @hide
403          */
clone()404         public Object clone() throws CloneNotSupportedException {
405             throw new CloneNotSupportedException();
406         }
407 
408         /**
409          * The metadata key for the content artwork / album art.
410          */
411         public final static int BITMAP_KEY_ARTWORK = 100;
412         /**
413          * @hide
414          * TODO(jmtrivi) have lockscreen and music move to the new key name
415          */
416         public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
417 
418         /**
419          * Adds textual information to be displayed.
420          * Note that none of the information added after {@link #apply()} has been called,
421          * will be displayed.
422          * @param key The identifier of a the metadata field to set. Valid values are
423          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
424          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
425          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
426          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
427          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
428          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
429          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
430          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
431          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
432          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
433          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
434          * @param value The text for the given key, or {@code null} to signify there is no valid
435          *      information for the field.
436          * @return Returns a reference to the same MetadataEditor object, so you can chain put
437          *      calls together.
438          */
putString(int key, String value)439         public synchronized MetadataEditor putString(int key, String value)
440                 throws IllegalArgumentException {
441             if (mApplied) {
442                 Log.e(TAG, "Can't edit a previously applied MetadataEditor");
443                 return this;
444             }
445             if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
446                 throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
447             }
448             mEditorMetadata.putString(String.valueOf(key), value);
449             mMetadataChanged = true;
450             return this;
451         }
452 
453         /**
454          * Adds numerical information to be displayed.
455          * Note that none of the information added after {@link #apply()} has been called,
456          * will be displayed.
457          * @param key the identifier of a the metadata field to set. Valid values are
458          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
459          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
460          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
461          *      expressed in milliseconds),
462          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
463          * @param value The long value for the given key
464          * @return Returns a reference to the same MetadataEditor object, so you can chain put
465          *      calls together.
466          * @throws IllegalArgumentException
467          */
putLong(int key, long value)468         public synchronized MetadataEditor putLong(int key, long value)
469                 throws IllegalArgumentException {
470             if (mApplied) {
471                 Log.e(TAG, "Can't edit a previously applied MetadataEditor");
472                 return this;
473             }
474             if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
475                 throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
476             }
477             mEditorMetadata.putLong(String.valueOf(key), value);
478             mMetadataChanged = true;
479             return this;
480         }
481 
482         /**
483          * Sets the album / artwork picture to be displayed on the remote control.
484          * @param key the identifier of the bitmap to set. The only valid value is
485          *      {@link #BITMAP_KEY_ARTWORK}
486          * @param bitmap The bitmap for the artwork, or null if there isn't any.
487          * @return Returns a reference to the same MetadataEditor object, so you can chain put
488          *      calls together.
489          * @throws IllegalArgumentException
490          * @see android.graphics.Bitmap
491          */
putBitmap(int key, Bitmap bitmap)492         public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
493                 throws IllegalArgumentException {
494             if (mApplied) {
495                 Log.e(TAG, "Can't edit a previously applied MetadataEditor");
496                 return this;
497             }
498             if (key != BITMAP_KEY_ARTWORK) {
499                 throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
500             }
501             if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) {
502                 mEditorArtwork = scaleBitmapIfTooBig(bitmap,
503                         mArtworkExpectedWidth, mArtworkExpectedHeight);
504             } else {
505                 // no valid resize dimensions, store as is
506                 mEditorArtwork = bitmap;
507             }
508             mArtworkChanged = true;
509             return this;
510         }
511 
512         /**
513          * Clears all the metadata that has been set since the MetadataEditor instance was
514          *     created with {@link RemoteControlClient#editMetadata(boolean)}.
515          */
clear()516         public synchronized void clear() {
517             if (mApplied) {
518                 Log.e(TAG, "Can't clear a previously applied MetadataEditor");
519                 return;
520             }
521             mEditorMetadata.clear();
522             mEditorArtwork = null;
523         }
524 
525         /**
526          * Associates all the metadata that has been set since the MetadataEditor instance was
527          *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
528          *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
529          *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
530          */
apply()531         public synchronized void apply() {
532             if (mApplied) {
533                 Log.e(TAG, "Can't apply a previously applied MetadataEditor");
534                 return;
535             }
536             synchronized(mCacheLock) {
537                 // assign the edited data
538                 mMetadata = new Bundle(mEditorMetadata);
539                 if ((mArtwork != null) && (!mArtwork.equals(mEditorArtwork))) {
540                     mArtwork.recycle();
541                 }
542                 mArtwork = mEditorArtwork;
543                 mEditorArtwork = null;
544                 if (mMetadataChanged & mArtworkChanged) {
545                     // send to remote control display if conditions are met
546                     sendMetadataWithArtwork_syncCacheLock();
547                 } else if (mMetadataChanged) {
548                     // send to remote control display if conditions are met
549                     sendMetadata_syncCacheLock();
550                 } else if (mArtworkChanged) {
551                     // send to remote control display if conditions are met
552                     sendArtwork_syncCacheLock();
553                 }
554                 mApplied = true;
555             }
556         }
557     }
558 
559     /**
560      * Creates a {@link MetadataEditor}.
561      * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
562      *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
563      * @return a new MetadataEditor instance.
564      */
editMetadata(boolean startEmpty)565     public MetadataEditor editMetadata(boolean startEmpty) {
566         MetadataEditor editor = new MetadataEditor();
567         if (startEmpty) {
568             editor.mEditorMetadata = new Bundle();
569             editor.mEditorArtwork = null;
570             editor.mMetadataChanged = true;
571             editor.mArtworkChanged = true;
572         } else {
573             editor.mEditorMetadata = new Bundle(mMetadata);
574             editor.mEditorArtwork = mArtwork;
575             editor.mMetadataChanged = false;
576             editor.mArtworkChanged = false;
577         }
578         return editor;
579     }
580 
581     /**
582      * Sets the current playback state.
583      * @param state The current playback state, one of the following values:
584      *       {@link #PLAYSTATE_STOPPED},
585      *       {@link #PLAYSTATE_PAUSED},
586      *       {@link #PLAYSTATE_PLAYING},
587      *       {@link #PLAYSTATE_FAST_FORWARDING},
588      *       {@link #PLAYSTATE_REWINDING},
589      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
590      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
591      *       {@link #PLAYSTATE_BUFFERING},
592      *       {@link #PLAYSTATE_ERROR}.
593      */
setPlaybackState(int state)594     public void setPlaybackState(int state) {
595         synchronized(mCacheLock) {
596             if (mPlaybackState != state) {
597                 // store locally
598                 mPlaybackState = state;
599                 // keep track of when the state change occurred
600                 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
601 
602                 // send to remote control display if conditions are met
603                 sendPlaybackState_syncCacheLock();
604                 // update AudioService
605                 sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state);
606             }
607         }
608     }
609 
610     /**
611      * Sets the flags for the media transport control buttons that this client supports.
612      * @param transportControlFlags A combination of the following flags:
613      *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
614      *      {@link #FLAG_KEY_MEDIA_REWIND},
615      *      {@link #FLAG_KEY_MEDIA_PLAY},
616      *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
617      *      {@link #FLAG_KEY_MEDIA_PAUSE},
618      *      {@link #FLAG_KEY_MEDIA_STOP},
619      *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
620      *      {@link #FLAG_KEY_MEDIA_NEXT}
621      */
setTransportControlFlags(int transportControlFlags)622     public void setTransportControlFlags(int transportControlFlags) {
623         synchronized(mCacheLock) {
624             // store locally
625             mTransportControlFlags = transportControlFlags;
626 
627             // send to remote control display if conditions are met
628             sendTransportControlFlags_syncCacheLock();
629         }
630     }
631 
632     /** @hide */
633     public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
634     /** @hide */
635     // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
636     public final static int DEFAULT_PLAYBACK_VOLUME = 15;
637 
638     private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
639     private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
640     private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
641     private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
642     private int mPlaybackStream = AudioManager.STREAM_MUSIC;
643 
644     /**
645      * @hide
646      * Set information describing information related to the playback of media so the system
647      * can implement additional behavior to handle non-local playback usecases.
648      * @param what a key to specify the type of information to set. Valid keys are
649      *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
650      *        {@link #PLAYBACKINFO_USES_STREAM},
651      *        {@link #PLAYBACKINFO_VOLUME},
652      *        {@link #PLAYBACKINFO_VOLUME_MAX},
653      *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
654      * @param value the value for the supplied information to set.
655      */
setPlaybackInformation(int what, int value)656     public void setPlaybackInformation(int what, int value) {
657         synchronized(mCacheLock) {
658             switch (what) {
659                 case PLAYBACKINFO_PLAYBACK_TYPE:
660                     if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
661                         if (mPlaybackType != value) {
662                             mPlaybackType = value;
663                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
664                         }
665                     } else {
666                         Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
667                     }
668                     break;
669                 case PLAYBACKINFO_VOLUME:
670                     if ((value > -1) && (value <= mPlaybackVolumeMax)) {
671                         if (mPlaybackVolume != value) {
672                             mPlaybackVolume = value;
673                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
674                         }
675                     } else {
676                         Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
677                     }
678                     break;
679                 case PLAYBACKINFO_VOLUME_MAX:
680                     if (value > 0) {
681                         if (mPlaybackVolumeMax != value) {
682                             mPlaybackVolumeMax = value;
683                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
684                         }
685                     } else {
686                         Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
687                     }
688                     break;
689                 case PLAYBACKINFO_USES_STREAM:
690                     if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
691                         mPlaybackStream = value;
692                     } else {
693                         Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
694                     }
695                     break;
696                 case PLAYBACKINFO_VOLUME_HANDLING:
697                     if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
698                         if (mPlaybackVolumeHandling != value) {
699                             mPlaybackVolumeHandling = value;
700                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
701                         }
702                     } else {
703                         Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
704                     }
705                     break;
706                 default:
707                     // not throwing an exception or returning an error if more keys are to be
708                     // supported in the future
709                     Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
710                     break;
711             }
712         }
713     }
714 
715     /**
716      * @hide
717      * Return playback information represented as an integer value.
718      * @param what a key to specify the type of information to retrieve. Valid keys are
719      *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
720      *        {@link #PLAYBACKINFO_USES_STREAM},
721      *        {@link #PLAYBACKINFO_VOLUME},
722      *        {@link #PLAYBACKINFO_VOLUME_MAX},
723      *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
724      * @return the current value for the given information type, or
725      *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
726      *   the value is unknown.
727      */
getIntPlaybackInformation(int what)728     public int getIntPlaybackInformation(int what) {
729         synchronized(mCacheLock) {
730             switch (what) {
731                 case PLAYBACKINFO_PLAYBACK_TYPE:
732                     return mPlaybackType;
733                 case PLAYBACKINFO_VOLUME:
734                     return mPlaybackVolume;
735                 case PLAYBACKINFO_VOLUME_MAX:
736                     return mPlaybackVolumeMax;
737                 case PLAYBACKINFO_USES_STREAM:
738                     return mPlaybackStream;
739                 case PLAYBACKINFO_VOLUME_HANDLING:
740                     return mPlaybackVolumeHandling;
741                 default:
742                     Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
743                     return PLAYBACKINFO_INVALID_VALUE;
744             }
745         }
746     }
747 
748     /**
749      * Lock for all cached data
750      */
751     private final Object mCacheLock = new Object();
752     /**
753      * Cache for the playback state.
754      * Access synchronized on mCacheLock
755      */
756     private int mPlaybackState = PLAYSTATE_NONE;
757     /**
758      * Time of last play state change
759      * Access synchronized on mCacheLock
760      */
761     private long mPlaybackStateChangeTimeMs = 0;
762     /**
763      * Cache for the artwork bitmap.
764      * Access synchronized on mCacheLock
765      * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
766      * accessed to be resized, in which case a copy will be made. This would add overhead in
767      * Bundle operations.
768      */
769     private Bitmap mArtwork;
770     private final int ARTWORK_DEFAULT_SIZE = 256;
771     private final int ARTWORK_INVALID_SIZE = -1;
772     private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
773     private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
774     /**
775      * Cache for the transport control mask.
776      * Access synchronized on mCacheLock
777      */
778     private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
779     /**
780      * Cache for the metadata strings.
781      * Access synchronized on mCacheLock
782      * This is re-initialized in apply() and so cannot be final.
783      */
784     private Bundle mMetadata = new Bundle();
785 
786     /**
787      * The current remote control client generation ID across the system
788      */
789     private int mCurrentClientGenId = -1;
790     /**
791      * The remote control client generation ID, the last time it was told it was the current RC.
792      * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
793      * client is the "focused" one, and that whenever this client's info is updated, it needs to
794      * send it to the known IRemoteControlDisplay interfaces.
795      */
796     private int mInternalClientGenId = -2;
797 
798     /**
799      * The media button intent description associated with this remote control client
800      * (can / should include target component for intent handling)
801      */
802     private final PendingIntent mRcMediaIntent;
803 
804     /**
805      * The remote control display to which this client will send information.
806      * NOTE: Only one IRemoteControlDisplay supported in this implementation
807      */
808     private IRemoteControlDisplay mRcDisplay;
809 
810     /**
811      * @hide
812      * Accessor to media button intent description (includes target component)
813      */
getRcMediaIntent()814     public PendingIntent getRcMediaIntent() {
815         return mRcMediaIntent;
816     }
817     /**
818      * @hide
819      * Accessor to IRemoteControlClient
820      */
getIRemoteControlClient()821     public IRemoteControlClient getIRemoteControlClient() {
822         return mIRCC;
823     }
824 
825     /**
826      * The IRemoteControlClient implementation
827      */
828     private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
829 
830         public void onInformationRequested(int clientGeneration, int infoFlags,
831                 int artWidth, int artHeight) {
832             // only post messages, we can't block here
833             if (mEventHandler != null) {
834                 // signal new client
835                 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
836                 mEventHandler.dispatchMessage(
837                         mEventHandler.obtainMessage(
838                                 MSG_NEW_INTERNAL_CLIENT_GEN,
839                                 artWidth, artHeight,
840                                 new Integer(clientGeneration)));
841                 // send the information
842                 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
843                 mEventHandler.removeMessages(MSG_REQUEST_METADATA);
844                 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
845                 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
846                 mEventHandler.dispatchMessage(
847                         mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
848                 mEventHandler.dispatchMessage(
849                         mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
850                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
851                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
852             }
853         }
854 
855         public void setCurrentClientGenerationId(int clientGeneration) {
856             // only post messages, we can't block here
857             if (mEventHandler != null) {
858                 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
859                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
860                         MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
861             }
862         }
863 
864         public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
865             // only post messages, we can't block here
866             if (mEventHandler != null) {
867                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
868                         MSG_PLUG_DISPLAY, rcd));
869             }
870         }
871 
872         public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
873             // only post messages, we can't block here
874             if (mEventHandler != null) {
875                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
876                         MSG_UNPLUG_DISPLAY, rcd));
877             }
878         }
879     };
880 
881     /**
882      * @hide
883      * Default value for the unique identifier
884      */
885     public final static int RCSE_ID_UNREGISTERED = -1;
886     /**
887      * Unique identifier of the RemoteControlStackEntry in AudioService with which
888      * this RemoteControlClient is associated.
889      */
890     private int mRcseId = RCSE_ID_UNREGISTERED;
891     /**
892      * @hide
893      * To be only used by AudioManager after it has received the unique id from
894      * IAudioService.registerRemoteControlClient()
895      * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
896      *              this RemoteControlClient is associated.
897      */
setRcseId(int id)898     public void setRcseId(int id) {
899         mRcseId = id;
900     }
901 
902     /**
903      * @hide
904      */
getRcseId()905     public int getRcseId() {
906         return mRcseId;
907     }
908 
909     private EventHandler mEventHandler;
910     private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
911     private final static int MSG_REQUEST_METADATA = 2;
912     private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
913     private final static int MSG_REQUEST_ARTWORK = 4;
914     private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
915     private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
916     private final static int MSG_PLUG_DISPLAY = 7;
917     private final static int MSG_UNPLUG_DISPLAY = 8;
918 
919     private class EventHandler extends Handler {
EventHandler(RemoteControlClient rcc, Looper looper)920         public EventHandler(RemoteControlClient rcc, Looper looper) {
921             super(looper);
922         }
923 
924         @Override
handleMessage(Message msg)925         public void handleMessage(Message msg) {
926             switch(msg.what) {
927                 case MSG_REQUEST_PLAYBACK_STATE:
928                     synchronized (mCacheLock) {
929                         sendPlaybackState_syncCacheLock();
930                     }
931                     break;
932                 case MSG_REQUEST_METADATA:
933                     synchronized (mCacheLock) {
934                         sendMetadata_syncCacheLock();
935                     }
936                     break;
937                 case MSG_REQUEST_TRANSPORTCONTROL:
938                     synchronized (mCacheLock) {
939                         sendTransportControlFlags_syncCacheLock();
940                     }
941                     break;
942                 case MSG_REQUEST_ARTWORK:
943                     synchronized (mCacheLock) {
944                         sendArtwork_syncCacheLock();
945                     }
946                     break;
947                 case MSG_NEW_INTERNAL_CLIENT_GEN:
948                     onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
949                     break;
950                 case MSG_NEW_CURRENT_CLIENT_GEN:
951                     onNewCurrentClientGen(msg.arg1);
952                     break;
953                 case MSG_PLUG_DISPLAY:
954                     onPlugDisplay((IRemoteControlDisplay)msg.obj);
955                     break;
956                 case MSG_UNPLUG_DISPLAY:
957                     onUnplugDisplay((IRemoteControlDisplay)msg.obj);
958                     break;
959                 default:
960                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
961             }
962         }
963     }
964 
965     //===========================================================
966     // Communication with IRemoteControlDisplay
967 
detachFromDisplay_syncCacheLock()968     private void detachFromDisplay_syncCacheLock() {
969         mRcDisplay = null;
970         mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
971         mArtworkExpectedHeight = ARTWORK_INVALID_SIZE;
972     }
973 
sendPlaybackState_syncCacheLock()974     private void sendPlaybackState_syncCacheLock() {
975         if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
976             try {
977                 mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState,
978                         mPlaybackStateChangeTimeMs);
979             } catch (RemoteException e) {
980                 Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
981                 detachFromDisplay_syncCacheLock();
982             }
983         }
984     }
985 
sendMetadata_syncCacheLock()986     private void sendMetadata_syncCacheLock() {
987         if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
988             try {
989                 mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
990             } catch (RemoteException e) {
991                 Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
992                 detachFromDisplay_syncCacheLock();
993             }
994         }
995     }
996 
sendTransportControlFlags_syncCacheLock()997     private void sendTransportControlFlags_syncCacheLock() {
998         if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
999             try {
1000                 mRcDisplay.setTransportControlFlags(mInternalClientGenId,
1001                         mTransportControlFlags);
1002             } catch (RemoteException e) {
1003                 Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
1004                 detachFromDisplay_syncCacheLock();
1005             }
1006         }
1007     }
1008 
sendArtwork_syncCacheLock()1009     private void sendArtwork_syncCacheLock() {
1010         if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
1011             // even though we have already scaled in setArtwork(), when this client needs to
1012             // send the bitmap, there might be newer and smaller expected dimensions, so we have
1013             // to check again.
1014             mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
1015             try {
1016                 mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
1017             } catch (RemoteException e) {
1018                 Log.e(TAG, "Error in sendArtwork(), dead display "+e);
1019                 detachFromDisplay_syncCacheLock();
1020             }
1021         }
1022     }
1023 
sendMetadataWithArtwork_syncCacheLock()1024     private void sendMetadataWithArtwork_syncCacheLock() {
1025         if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
1026             // even though we have already scaled in setArtwork(), when this client needs to
1027             // send the bitmap, there might be newer and smaller expected dimensions, so we have
1028             // to check again.
1029             mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
1030             try {
1031                 mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork);
1032             } catch (RemoteException e) {
1033                 Log.e(TAG, "Error in setAllMetadata(), dead display "+e);
1034                 detachFromDisplay_syncCacheLock();
1035             }
1036         }
1037     }
1038 
1039     //===========================================================
1040     // Communication with AudioService
1041 
1042     private static IAudioService sService;
1043 
getService()1044     private static IAudioService getService()
1045     {
1046         if (sService != null) {
1047             return sService;
1048         }
1049         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
1050         sService = IAudioService.Stub.asInterface(b);
1051         return sService;
1052     }
1053 
sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value)1054     private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
1055         if (mRcseId == RCSE_ID_UNREGISTERED) {
1056             return;
1057         }
1058         //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
1059         IAudioService service = getService();
1060         try {
1061             service.setPlaybackInfoForRcc(mRcseId, what, value);
1062         } catch (RemoteException e) {
1063             Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e);
1064         }
1065     }
1066 
1067     //===========================================================
1068     // Message handlers
1069 
onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight)1070     private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
1071         synchronized (mCacheLock) {
1072             // this remote control client is told it is the "focused" one:
1073             // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
1074             mInternalClientGenId = clientGeneration.intValue();
1075             if (artWidth > 0) {
1076                 mArtworkExpectedWidth = artWidth;
1077                 mArtworkExpectedHeight = artHeight;
1078             }
1079         }
1080     }
1081 
onNewCurrentClientGen(int clientGeneration)1082     private void onNewCurrentClientGen(int clientGeneration) {
1083         synchronized (mCacheLock) {
1084             mCurrentClientGenId = clientGeneration;
1085         }
1086     }
1087 
onPlugDisplay(IRemoteControlDisplay rcd)1088     private void onPlugDisplay(IRemoteControlDisplay rcd) {
1089         synchronized(mCacheLock) {
1090             mRcDisplay = rcd;
1091         }
1092     }
1093 
onUnplugDisplay(IRemoteControlDisplay rcd)1094     private void onUnplugDisplay(IRemoteControlDisplay rcd) {
1095         synchronized(mCacheLock) {
1096             if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) {
1097                 mRcDisplay = null;
1098                 mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
1099                 mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
1100             }
1101         }
1102     }
1103 
1104     //===========================================================
1105     // Internal utilities
1106 
1107     /**
1108      * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
1109      * If the bitmap fits, then do nothing and return the original.
1110      *
1111      * @param bitmap
1112      * @param maxWidth
1113      * @param maxHeight
1114      * @return
1115      */
1116 
scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight)1117     private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
1118         if (bitmap != null) {
1119             final int width = bitmap.getWidth();
1120             final int height = bitmap.getHeight();
1121             if (width > maxWidth || height > maxHeight) {
1122                 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
1123                 int newWidth = Math.round(scale * width);
1124                 int newHeight = Math.round(scale * height);
1125                 Bitmap.Config newConfig = bitmap.getConfig();
1126                 if (newConfig == null) {
1127                     newConfig = Bitmap.Config.ARGB_8888;
1128                 }
1129                 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
1130                 Canvas canvas = new Canvas(outBitmap);
1131                 Paint paint = new Paint();
1132                 paint.setAntiAlias(true);
1133                 paint.setFilterBitmap(true);
1134                 canvas.drawBitmap(bitmap, null,
1135                         new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
1136                 bitmap = outBitmap;
1137             }
1138         }
1139         return bitmap;
1140     }
1141 
1142     /**
1143      *  Fast routine to go through an array of allowed keys and return whether the key is part
1144      *  of that array
1145      * @param key the key value
1146      * @param validKeys the array of valid keys for a given type
1147      * @return true if the key is part of the array, false otherwise
1148      */
validTypeForKey(int key, int[] validKeys)1149     private static boolean validTypeForKey(int key, int[] validKeys) {
1150         try {
1151             for (int i = 0 ; ; i++) {
1152                 if (key == validKeys[i]) {
1153                     return true;
1154                 }
1155             }
1156         } catch (ArrayIndexOutOfBoundsException e) {
1157             return false;
1158         }
1159     }
1160 }
1161