Home | History | Annotate | Download | only in media
      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      */
    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      */
    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
    400         private MetadataEditor() { }
    401         /**
    402          * @hide
    403          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    814     public PendingIntent getRcMediaIntent() {
    815         return mRcMediaIntent;
    816     }
    817     /**
    818      * @hide
    819      * Accessor to IRemoteControlClient
    820      */
    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      */
    898     public void setRcseId(int id) {
    899         mRcseId = id;
    900     }
    901 
    902     /**
    903      * @hide
    904      */
    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 {
    920         public EventHandler(RemoteControlClient rcc, Looper looper) {
    921             super(looper);
    922         }
    923 
    924         @Override
    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 
    968     private void detachFromDisplay_syncCacheLock() {
    969         mRcDisplay = null;
    970         mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
    971         mArtworkExpectedHeight = ARTWORK_INVALID_SIZE;
    972     }
    973 
    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 
    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 
    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 
   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 
   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 
   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 
   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 
   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 
   1082     private void onNewCurrentClientGen(int clientGeneration) {
   1083         synchronized (mCacheLock) {
   1084             mCurrentClientGenId = clientGeneration;
   1085         }
   1086     }
   1087 
   1088     private void onPlugDisplay(IRemoteControlDisplay rcd) {
   1089         synchronized(mCacheLock) {
   1090             mRcDisplay = rcd;
   1091         }
   1092     }
   1093 
   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 
   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      */
   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