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.Parcelable;
     34 import android.os.RemoteException;
     35 import android.os.ServiceManager;
     36 import android.os.SystemClock;
     37 import android.util.Log;
     38 
     39 import java.lang.IllegalArgumentException;
     40 import java.util.ArrayList;
     41 import java.util.Iterator;
     42 
     43 /**
     44  * RemoteControlClient enables exposing information meant to be consumed by remote controls
     45  * capable of displaying metadata, artwork and media transport control buttons.
     46  *
     47  * <p>A remote control client object is associated with a media button event receiver. This
     48  * event receiver must have been previously registered with
     49  * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
     50  * RemoteControlClient can be registered through
     51  * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
     52  *
     53  * <p>Here is an example of creating a RemoteControlClient instance after registering a media
     54  * button event receiver:
     55  * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
     56  * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
     57  * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
     58  * // build the PendingIntent for the remote control client
     59  * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
     60  * mediaButtonIntent.setComponent(myEventReceiver);
     61  * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
     62  * // create and register the remote control client
     63  * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
     64  * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre>
     65  */
     66 public class RemoteControlClient
     67 {
     68     private final static String TAG = "RemoteControlClient";
     69     private final static boolean DEBUG = false;
     70 
     71     /**
     72      * Playback state of a RemoteControlClient which is stopped.
     73      *
     74      * @see #setPlaybackState(int)
     75      */
     76     public final static int PLAYSTATE_STOPPED            = 1;
     77     /**
     78      * Playback state of a RemoteControlClient which is paused.
     79      *
     80      * @see #setPlaybackState(int)
     81      */
     82     public final static int PLAYSTATE_PAUSED             = 2;
     83     /**
     84      * Playback state of a RemoteControlClient which is playing media.
     85      *
     86      * @see #setPlaybackState(int)
     87      */
     88     public final static int PLAYSTATE_PLAYING            = 3;
     89     /**
     90      * Playback state of a RemoteControlClient which is fast forwarding in the media
     91      *    it is currently playing.
     92      *
     93      * @see #setPlaybackState(int)
     94      */
     95     public final static int PLAYSTATE_FAST_FORWARDING    = 4;
     96     /**
     97      * Playback state of a RemoteControlClient which is fast rewinding in the media
     98      *    it is currently playing.
     99      *
    100      * @see #setPlaybackState(int)
    101      */
    102     public final static int PLAYSTATE_REWINDING          = 5;
    103     /**
    104      * Playback state of a RemoteControlClient which is skipping to the next
    105      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
    106      *
    107      * @see #setPlaybackState(int)
    108      */
    109     public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
    110     /**
    111      * Playback state of a RemoteControlClient which is skipping back to the previous
    112      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
    113      *
    114      * @see #setPlaybackState(int)
    115      */
    116     public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
    117     /**
    118      * Playback state of a RemoteControlClient which is buffering data to play before it can
    119      *    start or resume playback.
    120      *
    121      * @see #setPlaybackState(int)
    122      */
    123     public final static int PLAYSTATE_BUFFERING          = 8;
    124     /**
    125      * Playback state of a RemoteControlClient which cannot perform any playback related
    126      *    operation because of an internal error. Examples of such situations are no network
    127      *    connectivity when attempting to stream data from a server, or expired user credentials
    128      *    when trying to play subscription-based content.
    129      *
    130      * @see #setPlaybackState(int)
    131      */
    132     public final static int PLAYSTATE_ERROR              = 9;
    133     /**
    134      * @hide
    135      * The value of a playback state when none has been declared.
    136      * Intentionally hidden as an application shouldn't set such a playback state value.
    137      */
    138     public final static int PLAYSTATE_NONE               = 0;
    139 
    140     /**
    141      * @hide
    142      * The default playback type, "local", indicating the presentation of the media is happening on
    143      * the same device (e.g. a phone, a tablet) as where it is controlled from.
    144      */
    145     public final static int PLAYBACK_TYPE_LOCAL = 0;
    146     /**
    147      * @hide
    148      * A playback type indicating the presentation of the media is happening on
    149      * a different device (i.e. the remote device) than where it is controlled from.
    150      */
    151     public final static int PLAYBACK_TYPE_REMOTE = 1;
    152     private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
    153     private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
    154     /**
    155      * @hide
    156      * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
    157      * from this object. An example of fixed playback volume is a remote player, playing over HDMI
    158      * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
    159      * source.
    160      * @see #PLAYBACKINFO_VOLUME_HANDLING.
    161      */
    162     public final static int PLAYBACK_VOLUME_FIXED = 0;
    163     /**
    164      * @hide
    165      * Playback information indicating the playback volume is variable and can be controlled from
    166      * this object.
    167      * @see #PLAYBACKINFO_VOLUME_HANDLING.
    168      */
    169     public final static int PLAYBACK_VOLUME_VARIABLE = 1;
    170     /**
    171      * @hide (to be un-hidden)
    172      * The playback information value indicating the value of a given information type is invalid.
    173      * @see #PLAYBACKINFO_VOLUME_HANDLING.
    174      */
    175     public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
    176 
    177     /**
    178      * @hide
    179      * An unknown or invalid playback position value.
    180      */
    181     public final static long PLAYBACK_POSITION_INVALID = -1;
    182     /**
    183      * @hide
    184      * An invalid playback position value associated with the use of {@link #setPlaybackState(int)}
    185      * used to indicate that playback position will remain unknown.
    186      */
    187     public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L;
    188     /**
    189      * @hide
    190      * The default playback speed, 1x.
    191      */
    192     public final static float PLAYBACK_SPEED_1X = 1.0f;
    193 
    194     //==========================================
    195     // Public keys for playback information
    196     /**
    197      * @hide
    198      * Playback information that defines the type of playback associated with this
    199      * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
    200      */
    201     public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
    202     /**
    203      * @hide
    204      * Playback information that defines at what volume the playback associated with this
    205      * RemoteControlClient is performed. This information is only used when the playback type is not
    206      * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
    207      */
    208     public final static int PLAYBACKINFO_VOLUME = 2;
    209     /**
    210      * @hide
    211      * Playback information that defines the maximum volume volume value that is supported
    212      * by the playback associated with this RemoteControlClient. This information is only used
    213      * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
    214      */
    215     public final static int PLAYBACKINFO_VOLUME_MAX = 3;
    216     /**
    217      * @hide
    218      * Playback information that defines how volume is handled for the presentation of the media.
    219      * @see #PLAYBACK_VOLUME_FIXED
    220      * @see #PLAYBACK_VOLUME_VARIABLE
    221      */
    222     public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
    223     /**
    224      * @hide
    225      * Playback information that defines over what stream type the media is presented.
    226      */
    227     public final static int PLAYBACKINFO_USES_STREAM = 5;
    228 
    229     //==========================================
    230     // Public flags for the supported transport control capabilities
    231     /**
    232      * Flag indicating a RemoteControlClient makes use of the "previous" media key.
    233      *
    234      * @see #setTransportControlFlags(int)
    235      * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
    236      */
    237     public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
    238     /**
    239      * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
    240      *
    241      * @see #setTransportControlFlags(int)
    242      * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
    243      */
    244     public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
    245     /**
    246      * Flag indicating a RemoteControlClient makes use of the "play" media key.
    247      *
    248      * @see #setTransportControlFlags(int)
    249      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
    250      */
    251     public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
    252     /**
    253      * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
    254      *
    255      * @see #setTransportControlFlags(int)
    256      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
    257      */
    258     public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
    259     /**
    260      * Flag indicating a RemoteControlClient makes use of the "pause" media key.
    261      *
    262      * @see #setTransportControlFlags(int)
    263      * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
    264      */
    265     public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
    266     /**
    267      * Flag indicating a RemoteControlClient makes use of the "stop" media key.
    268      *
    269      * @see #setTransportControlFlags(int)
    270      * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
    271      */
    272     public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
    273     /**
    274      * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
    275      *
    276      * @see #setTransportControlFlags(int)
    277      * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
    278      */
    279     public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
    280     /**
    281      * Flag indicating a RemoteControlClient makes use of the "next" media key.
    282      *
    283      * @see #setTransportControlFlags(int)
    284      * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
    285      */
    286     public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
    287     /**
    288      * Flag indicating a RemoteControlClient can receive changes in the media playback position
    289      * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set
    290      * in order for components that display the RemoteControlClient information, to display and
    291      * let the user control media playback position.
    292      * @see #setTransportControlFlags(int)
    293      * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener)
    294      * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener)
    295      */
    296     public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
    297     /**
    298      * Flag indicating a RemoteControlClient supports ratings.
    299      * This flag must be set in order for components that display the RemoteControlClient
    300      * information, to display ratings information, and, if ratings are declared editable
    301      * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the
    302      * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate
    303      * the media, with values being received through the interface set with
    304      * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}.
    305      * @see #setTransportControlFlags(int)
    306      */
    307     public final static int FLAG_KEY_MEDIA_RATING = 1 << 9;
    308 
    309     /**
    310      * @hide
    311      * The flags for when no media keys are declared supported.
    312      * Intentionally hidden as an application shouldn't set the transport control flags
    313      *     to this value.
    314      */
    315     public final static int FLAGS_KEY_MEDIA_NONE = 0;
    316 
    317     /**
    318      * @hide
    319      * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
    320      */
    321     public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
    322     /**
    323      * @hide
    324      * Flag used to signal that the transport control buttons supported by the
    325      *     RemoteControlClient are requested.
    326      * This can for instance happen when playback is at the end of a playlist, and the "next"
    327      * operation is not supported anymore.
    328      */
    329     public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
    330     /**
    331      * @hide
    332      * Flag used to signal that the playback state of the RemoteControlClient is requested.
    333      */
    334     public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
    335     /**
    336      * @hide
    337      * Flag used to signal that the album art for the RemoteControlClient is requested.
    338      */
    339     public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
    340 
    341     /**
    342      * Class constructor.
    343      * @param mediaButtonIntent The intent that will be sent for the media button events sent
    344      *     by remote controls.
    345      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
    346      *     action, and have a component that will handle the intent (set with
    347      *     {@link Intent#setComponent(ComponentName)}) registered with
    348      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
    349      *     before this new RemoteControlClient can itself be registered with
    350      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
    351      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
    352      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
    353      */
    354     public RemoteControlClient(PendingIntent mediaButtonIntent) {
    355         mRcMediaIntent = mediaButtonIntent;
    356 
    357         Looper looper;
    358         if ((looper = Looper.myLooper()) != null) {
    359             mEventHandler = new EventHandler(this, looper);
    360         } else if ((looper = Looper.getMainLooper()) != null) {
    361             mEventHandler = new EventHandler(this, looper);
    362         } else {
    363             mEventHandler = null;
    364             Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
    365         }
    366     }
    367 
    368     /**
    369      * Class constructor for a remote control client whose internal event handling
    370      * happens on a user-provided Looper.
    371      * @param mediaButtonIntent The intent that will be sent for the media button events sent
    372      *     by remote controls.
    373      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
    374      *     action, and have a component that will handle the intent (set with
    375      *     {@link Intent#setComponent(ComponentName)}) registered with
    376      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
    377      *     before this new RemoteControlClient can itself be registered with
    378      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
    379      * @param looper The Looper running the event loop.
    380      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
    381      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
    382      */
    383     public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
    384         mRcMediaIntent = mediaButtonIntent;
    385 
    386         mEventHandler = new EventHandler(this, looper);
    387     }
    388 
    389     /**
    390      * Class used to modify metadata in a {@link RemoteControlClient} object.
    391      * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
    392      * on which you set the metadata for the RemoteControlClient instance. Once all the information
    393      * has been set, use {@link #apply()} to make it the new metadata that should be displayed
    394      * for the associated client. Once the metadata has been "applied", you cannot reuse this
    395      * instance of the MetadataEditor.
    396      */
    397     public class MetadataEditor extends MediaMetadataEditor {
    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         /**
    414          * @hide
    415          * TODO(jmtrivi) have lockscreen move to the new key name and remove
    416          */
    417         public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
    418 
    419         /**
    420          * Adds textual information to be displayed.
    421          * Note that none of the information added after {@link #apply()} has been called,
    422          * will be displayed.
    423          * @param key The identifier of a the metadata field to set. Valid values are
    424          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
    425          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
    426          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
    427          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
    428          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
    429          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
    430          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
    431          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
    432          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
    433          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
    434          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
    435          * @param value The text for the given key, or {@code null} to signify there is no valid
    436          *      information for the field.
    437          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    438          *      calls together.
    439          */
    440         public synchronized MetadataEditor putString(int key, String value)
    441                 throws IllegalArgumentException {
    442             super.putString(key, value);
    443             return this;
    444         }
    445 
    446         /**
    447          * Adds numerical information to be displayed.
    448          * Note that none of the information added after {@link #apply()} has been called,
    449          * will be displayed.
    450          * @param key the identifier of a the metadata field to set. Valid values are
    451          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
    452          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
    453          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
    454          *      expressed in milliseconds),
    455          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
    456          * @param value The long value for the given key
    457          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    458          *      calls together.
    459          * @throws IllegalArgumentException
    460          */
    461         public synchronized MetadataEditor putLong(int key, long value)
    462                 throws IllegalArgumentException {
    463             super.putLong(key, value);
    464             return this;
    465         }
    466 
    467         /**
    468          * Sets the album / artwork picture to be displayed on the remote control.
    469          * @param key the identifier of the bitmap to set. The only valid value is
    470          *      {@link #BITMAP_KEY_ARTWORK}
    471          * @param bitmap The bitmap for the artwork, or null if there isn't any.
    472          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    473          *      calls together.
    474          * @throws IllegalArgumentException
    475          * @see android.graphics.Bitmap
    476          */
    477         @Override
    478         public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
    479                 throws IllegalArgumentException {
    480             super.putBitmap(key, bitmap);
    481             return this;
    482         }
    483 
    484         /**
    485          * Clears all the metadata that has been set since the MetadataEditor instance was created
    486          * (with {@link RemoteControlClient#editMetadata(boolean)}).
    487          * Note that clearing the metadata doesn't reset the editable keys
    488          * (use {@link MediaMetadataEditor#removeEditableKeys()} instead).
    489          */
    490         @Override
    491         public synchronized void clear() {
    492             super.clear();
    493         }
    494 
    495         /**
    496          * Associates all the metadata that has been set since the MetadataEditor instance was
    497          *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
    498          *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
    499          *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
    500          */
    501         public synchronized void apply() {
    502             if (mApplied) {
    503                 Log.e(TAG, "Can't apply a previously applied MetadataEditor");
    504                 return;
    505             }
    506             synchronized(mCacheLock) {
    507                 // assign the edited data
    508                 mMetadata = new Bundle(mEditorMetadata);
    509                 // add the information about editable keys
    510                 mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys);
    511                 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
    512                     mOriginalArtwork.recycle();
    513                 }
    514                 mOriginalArtwork = mEditorArtwork;
    515                 mEditorArtwork = null;
    516                 if (mMetadataChanged & mArtworkChanged) {
    517                     // send to remote control display if conditions are met
    518                     sendMetadataWithArtwork_syncCacheLock(null, 0, 0);
    519                 } else if (mMetadataChanged) {
    520                     // send to remote control display if conditions are met
    521                     sendMetadata_syncCacheLock(null);
    522                 } else if (mArtworkChanged) {
    523                     // send to remote control display if conditions are met
    524                     sendArtwork_syncCacheLock(null, 0, 0);
    525                 }
    526                 mApplied = true;
    527             }
    528         }
    529     }
    530 
    531     /**
    532      * Creates a {@link MetadataEditor}.
    533      * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
    534      *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
    535      * @return a new MetadataEditor instance.
    536      */
    537     public MetadataEditor editMetadata(boolean startEmpty) {
    538         MetadataEditor editor = new MetadataEditor();
    539         if (startEmpty) {
    540             editor.mEditorMetadata = new Bundle();
    541             editor.mEditorArtwork = null;
    542             editor.mMetadataChanged = true;
    543             editor.mArtworkChanged = true;
    544             editor.mEditableKeys = 0;
    545         } else {
    546             editor.mEditorMetadata = new Bundle(mMetadata);
    547             editor.mEditorArtwork = mOriginalArtwork;
    548             editor.mMetadataChanged = false;
    549             editor.mArtworkChanged = false;
    550         }
    551         return editor;
    552     }
    553 
    554     /**
    555      * Sets the current playback state.
    556      * @param state The current playback state, one of the following values:
    557      *       {@link #PLAYSTATE_STOPPED},
    558      *       {@link #PLAYSTATE_PAUSED},
    559      *       {@link #PLAYSTATE_PLAYING},
    560      *       {@link #PLAYSTATE_FAST_FORWARDING},
    561      *       {@link #PLAYSTATE_REWINDING},
    562      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
    563      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
    564      *       {@link #PLAYSTATE_BUFFERING},
    565      *       {@link #PLAYSTATE_ERROR}.
    566      */
    567     public void setPlaybackState(int state) {
    568         setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X,
    569                 false /* legacy API, converting to method with position and speed */);
    570     }
    571 
    572     /**
    573      * Sets the current playback state and the matching media position for the current playback
    574      *   speed.
    575      * @param state The current playback state, one of the following values:
    576      *       {@link #PLAYSTATE_STOPPED},
    577      *       {@link #PLAYSTATE_PAUSED},
    578      *       {@link #PLAYSTATE_PLAYING},
    579      *       {@link #PLAYSTATE_FAST_FORWARDING},
    580      *       {@link #PLAYSTATE_REWINDING},
    581      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
    582      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
    583      *       {@link #PLAYSTATE_BUFFERING},
    584      *       {@link #PLAYSTATE_ERROR}.
    585      * @param timeInMs a 0 or positive value for the current media position expressed in ms
    586      *    (same unit as for when sending the media duration, if applicable, with
    587      *    {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the
    588      *    {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not
    589      *    known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state
    590      *    is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
    591      * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
    592      *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
    593      *    playing (e.g. when state is {@link #PLAYSTATE_ERROR}).
    594      */
    595     public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
    596         setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
    597     }
    598 
    599     private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed,
    600             boolean hasPosition) {
    601         synchronized(mCacheLock) {
    602             if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
    603                     || (mPlaybackSpeed != playbackSpeed)) {
    604                 // store locally
    605                 mPlaybackState = state;
    606                 // distinguish between an application not knowing the current playback position
    607                 // at the moment and an application using the API where only the playback state
    608                 // is passed, not the playback position.
    609                 if (hasPosition) {
    610                     if (timeInMs < 0) {
    611                         mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
    612                     } else {
    613                         mPlaybackPositionMs = timeInMs;
    614                     }
    615                 } else {
    616                     mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN;
    617                 }
    618                 mPlaybackSpeed = playbackSpeed;
    619                 // keep track of when the state change occurred
    620                 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
    621 
    622                 // send to remote control display if conditions are met
    623                 sendPlaybackState_syncCacheLock(null);
    624                 // update AudioService
    625                 sendAudioServiceNewPlaybackState_syncCacheLock();
    626 
    627                 // handle automatic playback position refreshes
    628                 initiateCheckForDrift_syncCacheLock();
    629             }
    630         }
    631     }
    632 
    633     private void initiateCheckForDrift_syncCacheLock() {
    634         if (mEventHandler == null) {
    635             return;
    636         }
    637         mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
    638         if (!mNeedsPositionSync) {
    639             return;
    640         }
    641         if (mPlaybackPositionMs < 0) {
    642             // the current playback state has no known playback position, it's no use
    643             // trying to see if there is any drift at this point
    644             // (this also bypasses this mechanism for older apps that use the old
    645             //  setPlaybackState(int) API)
    646             return;
    647         }
    648         if (playbackPositionShouldMove(mPlaybackState)) {
    649             // playback position moving, schedule next position drift check
    650             mEventHandler.sendMessageDelayed(
    651                     mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
    652                     getCheckPeriodFromSpeed(mPlaybackSpeed));
    653         }
    654     }
    655 
    656     private void onPositionDriftCheck() {
    657         if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
    658         synchronized(mCacheLock) {
    659             if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
    660                 return;
    661             }
    662             if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
    663                 if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
    664                 return;
    665             }
    666             long estPos = mPlaybackPositionMs + (long)
    667                     ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
    668             long actPos = mPositionProvider.onGetPlaybackPosition();
    669             if (actPos >= 0) {
    670                 if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
    671                     // drift happened, report the new position
    672                     if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +"  est=" +estPos); }
    673                     setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
    674                 } else {
    675                     if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +"  est=" + estPos); }
    676                     // no drift, schedule the next drift check
    677                     mEventHandler.sendMessageDelayed(
    678                             mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
    679                             getCheckPeriodFromSpeed(mPlaybackSpeed));
    680                 }
    681             } else {
    682                 // invalid position (negative value), can't check for drift
    683                 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
    684             }
    685         }
    686     }
    687 
    688     /**
    689      * Sets the flags for the media transport control buttons that this client supports.
    690      * @param transportControlFlags A combination of the following flags:
    691      *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
    692      *      {@link #FLAG_KEY_MEDIA_REWIND},
    693      *      {@link #FLAG_KEY_MEDIA_PLAY},
    694      *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
    695      *      {@link #FLAG_KEY_MEDIA_PAUSE},
    696      *      {@link #FLAG_KEY_MEDIA_STOP},
    697      *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
    698      *      {@link #FLAG_KEY_MEDIA_NEXT},
    699      *      {@link #FLAG_KEY_MEDIA_POSITION_UPDATE},
    700      *      {@link #FLAG_KEY_MEDIA_RATING}.
    701      */
    702     public void setTransportControlFlags(int transportControlFlags) {
    703         synchronized(mCacheLock) {
    704             // store locally
    705             mTransportControlFlags = transportControlFlags;
    706 
    707             // send to remote control display if conditions are met
    708             sendTransportControlInfo_syncCacheLock(null);
    709         }
    710     }
    711 
    712     /**
    713      * Interface definition for a callback to be invoked when one of the metadata values has
    714      * been updated.
    715      * Implement this interface to receive metadata updates after registering your listener
    716      * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}.
    717      */
    718     public interface OnMetadataUpdateListener {
    719         /**
    720          * Called on the implementer to notify that the metadata field for the given key has
    721          * been updated to the new value.
    722          * @param key the identifier of the updated metadata field.
    723          * @param newValue the Object storing the new value for the key.
    724          */
    725         public abstract void onMetadataUpdate(int key, Object newValue);
    726     }
    727 
    728     /**
    729      * Sets the listener to be called whenever the metadata is updated.
    730      * New metadata values will be received in the same thread as the one in which
    731      * RemoteControlClient was created.
    732      * @param l the metadata update listener
    733      */
    734     public void setMetadataUpdateListener(OnMetadataUpdateListener l) {
    735         synchronized(mCacheLock) {
    736             mMetadataUpdateListener = l;
    737         }
    738     }
    739 
    740 
    741     /**
    742      * Interface definition for a callback to be invoked when the media playback position is
    743      * requested to be updated.
    744      * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
    745      */
    746     public interface OnPlaybackPositionUpdateListener {
    747         /**
    748          * Called on the implementer to notify it that the playback head should be set at the given
    749          * position. If the position can be changed from its current value, the implementor of
    750          * the interface must also update the playback position using
    751          * {@link #setPlaybackState(int, long, float)} to reflect the actual new
    752          * position being used, regardless of whether it differs from the requested position.
    753          * Failure to do so would cause the system to not know the new actual playback position,
    754          * and user interface components would fail to show the user where playback resumed after
    755          * the position was updated.
    756          * @param newPositionMs the new requested position in the current media, expressed in ms.
    757          */
    758         void onPlaybackPositionUpdate(long newPositionMs);
    759     }
    760 
    761     /**
    762      * Interface definition for a callback to be invoked when the media playback position is
    763      * queried.
    764      * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
    765      */
    766     public interface OnGetPlaybackPositionListener {
    767         /**
    768          * Called on the implementer of the interface to query the current playback position.
    769          * @return a negative value if the current playback position (or the last valid playback
    770          *     position) is not known, or a zero or positive value expressed in ms indicating the
    771          *     current position, or the last valid known position.
    772          */
    773         long onGetPlaybackPosition();
    774     }
    775 
    776     /**
    777      * Sets the listener to be called whenever the media playback position is requested
    778      * to be updated.
    779      * Notifications will be received in the same thread as the one in which RemoteControlClient
    780      * was created.
    781      * @param l the position update listener to be called
    782      */
    783     public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
    784         synchronized(mCacheLock) {
    785             int oldCapa = mPlaybackPositionCapabilities;
    786             if (l != null) {
    787                 mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE;
    788             } else {
    789                 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE;
    790             }
    791             mPositionUpdateListener = l;
    792             if (oldCapa != mPlaybackPositionCapabilities) {
    793                 // tell RCDs that this RCC's playback position capabilities have changed
    794                 sendTransportControlInfo_syncCacheLock(null);
    795             }
    796         }
    797     }
    798 
    799     /**
    800      * Sets the listener to be called whenever the media current playback position is needed.
    801      * Queries will be received in the same thread as the one in which RemoteControlClient
    802      * was created.
    803      * @param l the listener to be called to retrieve the playback position
    804      */
    805     public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
    806         synchronized(mCacheLock) {
    807             int oldCapa = mPlaybackPositionCapabilities;
    808             if (l != null) {
    809                 mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE;
    810             } else {
    811                 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE;
    812             }
    813             mPositionProvider = l;
    814             if (oldCapa != mPlaybackPositionCapabilities) {
    815                 // tell RCDs that this RCC's playback position capabilities have changed
    816                 sendTransportControlInfo_syncCacheLock(null);
    817             }
    818             if ((mPositionProvider != null) && (mEventHandler != null)
    819                     && playbackPositionShouldMove(mPlaybackState)) {
    820                 // playback position is already moving, but now we have a position provider,
    821                 // so schedule a drift check right now
    822                 mEventHandler.sendMessageDelayed(
    823                         mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
    824                         0 /*check now*/);
    825             }
    826         }
    827     }
    828 
    829     /**
    830      * @hide
    831      * Flag to reflect that the application controlling this RemoteControlClient sends playback
    832      * position updates. The playback position being "readable" is considered from the application's
    833      * point of view.
    834      */
    835     public static int MEDIA_POSITION_READABLE = 1 << 0;
    836     /**
    837      * @hide
    838      * Flag to reflect that the application controlling this RemoteControlClient can receive
    839      * playback position updates. The playback position being "writable"
    840      * is considered from the application's point of view.
    841      */
    842     public static int MEDIA_POSITION_WRITABLE = 1 << 1;
    843 
    844     private int mPlaybackPositionCapabilities = 0;
    845 
    846     /** @hide */
    847     public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
    848     /** @hide */
    849     // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
    850     public final static int DEFAULT_PLAYBACK_VOLUME = 15;
    851 
    852     private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
    853     private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
    854     private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
    855     private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
    856     private int mPlaybackStream = AudioManager.STREAM_MUSIC;
    857 
    858     /**
    859      * @hide
    860      * Set information describing information related to the playback of media so the system
    861      * can implement additional behavior to handle non-local playback usecases.
    862      * @param what a key to specify the type of information to set. Valid keys are
    863      *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
    864      *        {@link #PLAYBACKINFO_USES_STREAM},
    865      *        {@link #PLAYBACKINFO_VOLUME},
    866      *        {@link #PLAYBACKINFO_VOLUME_MAX},
    867      *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
    868      * @param value the value for the supplied information to set.
    869      */
    870     public void setPlaybackInformation(int what, int value) {
    871         synchronized(mCacheLock) {
    872             switch (what) {
    873                 case PLAYBACKINFO_PLAYBACK_TYPE:
    874                     if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
    875                         if (mPlaybackType != value) {
    876                             mPlaybackType = value;
    877                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
    878                         }
    879                     } else {
    880                         Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
    881                     }
    882                     break;
    883                 case PLAYBACKINFO_VOLUME:
    884                     if ((value > -1) && (value <= mPlaybackVolumeMax)) {
    885                         if (mPlaybackVolume != value) {
    886                             mPlaybackVolume = value;
    887                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
    888                         }
    889                     } else {
    890                         Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
    891                     }
    892                     break;
    893                 case PLAYBACKINFO_VOLUME_MAX:
    894                     if (value > 0) {
    895                         if (mPlaybackVolumeMax != value) {
    896                             mPlaybackVolumeMax = value;
    897                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
    898                         }
    899                     } else {
    900                         Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
    901                     }
    902                     break;
    903                 case PLAYBACKINFO_USES_STREAM:
    904                     if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
    905                         mPlaybackStream = value;
    906                     } else {
    907                         Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
    908                     }
    909                     break;
    910                 case PLAYBACKINFO_VOLUME_HANDLING:
    911                     if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
    912                         if (mPlaybackVolumeHandling != value) {
    913                             mPlaybackVolumeHandling = value;
    914                             sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
    915                         }
    916                     } else {
    917                         Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
    918                     }
    919                     break;
    920                 default:
    921                     // not throwing an exception or returning an error if more keys are to be
    922                     // supported in the future
    923                     Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
    924                     break;
    925             }
    926         }
    927     }
    928 
    929     /**
    930      * @hide
    931      * Return playback information represented as an integer value.
    932      * @param what a key to specify the type of information to retrieve. Valid keys are
    933      *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
    934      *        {@link #PLAYBACKINFO_USES_STREAM},
    935      *        {@link #PLAYBACKINFO_VOLUME},
    936      *        {@link #PLAYBACKINFO_VOLUME_MAX},
    937      *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
    938      * @return the current value for the given information type, or
    939      *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
    940      *   the value is unknown.
    941      */
    942     public int getIntPlaybackInformation(int what) {
    943         synchronized(mCacheLock) {
    944             switch (what) {
    945                 case PLAYBACKINFO_PLAYBACK_TYPE:
    946                     return mPlaybackType;
    947                 case PLAYBACKINFO_VOLUME:
    948                     return mPlaybackVolume;
    949                 case PLAYBACKINFO_VOLUME_MAX:
    950                     return mPlaybackVolumeMax;
    951                 case PLAYBACKINFO_USES_STREAM:
    952                     return mPlaybackStream;
    953                 case PLAYBACKINFO_VOLUME_HANDLING:
    954                     return mPlaybackVolumeHandling;
    955                 default:
    956                     Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
    957                     return PLAYBACKINFO_INVALID_VALUE;
    958             }
    959         }
    960     }
    961 
    962     /**
    963      * Lock for all cached data
    964      */
    965     private final Object mCacheLock = new Object();
    966     /**
    967      * Cache for the playback state.
    968      * Access synchronized on mCacheLock
    969      */
    970     private int mPlaybackState = PLAYSTATE_NONE;
    971     /**
    972      * Time of last play state change
    973      * Access synchronized on mCacheLock
    974      */
    975     private long mPlaybackStateChangeTimeMs = 0;
    976     /**
    977      * Last playback position in ms reported by the user
    978      */
    979     private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
    980     /**
    981      * Last playback speed reported by the user
    982      */
    983     private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
    984     /**
    985      * Cache for the artwork bitmap.
    986      * Access synchronized on mCacheLock
    987      * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
    988      * accessed to be resized, in which case a copy will be made. This would add overhead in
    989      * Bundle operations.
    990      */
    991     private Bitmap mOriginalArtwork;
    992     /**
    993      * Cache for the transport control mask.
    994      * Access synchronized on mCacheLock
    995      */
    996     private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
    997     /**
    998      * Cache for the metadata strings.
    999      * Access synchronized on mCacheLock
   1000      * This is re-initialized in apply() and so cannot be final.
   1001      */
   1002     private Bundle mMetadata = new Bundle();
   1003     /**
   1004      * Listener registered by user of RemoteControlClient to receive requests for playback position
   1005      * update requests.
   1006      */
   1007     private OnPlaybackPositionUpdateListener mPositionUpdateListener;
   1008     /**
   1009      * Provider registered by user of RemoteControlClient to provide the current playback position.
   1010      */
   1011     private OnGetPlaybackPositionListener mPositionProvider;
   1012     /**
   1013      * Listener registered by user of RemoteControlClient to receive edit changes to metadata
   1014      * it exposes.
   1015      */
   1016     private OnMetadataUpdateListener mMetadataUpdateListener;
   1017     /**
   1018      * The current remote control client generation ID across the system, as known by this object
   1019      */
   1020     private int mCurrentClientGenId = -1;
   1021     /**
   1022      * The remote control client generation ID, the last time it was told it was the current RC.
   1023      * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
   1024      * client is the "focused" one, and that whenever this client's info is updated, it needs to
   1025      * send it to the known IRemoteControlDisplay interfaces.
   1026      */
   1027     private int mInternalClientGenId = -2;
   1028 
   1029     /**
   1030      * The media button intent description associated with this remote control client
   1031      * (can / should include target component for intent handling, used when persisting media
   1032      *    button event receiver across reboots).
   1033      */
   1034     private final PendingIntent mRcMediaIntent;
   1035 
   1036     /**
   1037      * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true.
   1038      */
   1039     // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead
   1040     private boolean mNeedsPositionSync = false;
   1041 
   1042     /**
   1043      * A class to encapsulate all the information about a remote control display.
   1044      * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
   1045      */
   1046     private class DisplayInfoForClient {
   1047         /** may never be null */
   1048         private IRemoteControlDisplay mRcDisplay;
   1049         private int mArtworkExpectedWidth;
   1050         private int mArtworkExpectedHeight;
   1051         private boolean mWantsPositionSync = false;
   1052         private boolean mEnabled = true;
   1053 
   1054         DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
   1055             mRcDisplay = rcd;
   1056             mArtworkExpectedWidth = w;
   1057             mArtworkExpectedHeight = h;
   1058         }
   1059     }
   1060 
   1061     /**
   1062      * The list of remote control displays to which this client will send information.
   1063      * Accessed and modified synchronized on mCacheLock
   1064      */
   1065     private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1);
   1066 
   1067     /**
   1068      * @hide
   1069      * Accessor to media button intent description (includes target component)
   1070      */
   1071     public PendingIntent getRcMediaIntent() {
   1072         return mRcMediaIntent;
   1073     }
   1074     /**
   1075      * @hide
   1076      * Accessor to IRemoteControlClient
   1077      */
   1078     public IRemoteControlClient getIRemoteControlClient() {
   1079         return mIRCC;
   1080     }
   1081 
   1082     /**
   1083      * The IRemoteControlClient implementation
   1084      */
   1085     private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
   1086 
   1087         //TODO change name to informationRequestForAllDisplays()
   1088         public void onInformationRequested(int generationId, int infoFlags) {
   1089             // only post messages, we can't block here
   1090             if (mEventHandler != null) {
   1091                 // signal new client
   1092                 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
   1093                 mEventHandler.sendMessage(
   1094                         mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
   1095                                 /*arg1*/ generationId, /*arg2, ignored*/ 0));
   1096                 // send the information
   1097                 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
   1098                 mEventHandler.removeMessages(MSG_REQUEST_METADATA);
   1099                 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
   1100                 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
   1101                 mEventHandler.removeMessages(MSG_REQUEST_METADATA_ARTWORK);
   1102                 mEventHandler.sendMessage(
   1103                         mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, null));
   1104                 mEventHandler.sendMessage(
   1105                         mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, null));
   1106                 mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK,
   1107                         0, 0, null));
   1108             }
   1109         }
   1110 
   1111         public void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h) {
   1112             // only post messages, we can't block here
   1113             if (mEventHandler != null) {
   1114                 mEventHandler.sendMessage(
   1115                         mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, rcd));
   1116                 mEventHandler.sendMessage(
   1117                         mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, rcd));
   1118                 if ((w > 0) && (h > 0)) {
   1119                     mEventHandler.sendMessage(
   1120                             mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK, w, h, rcd));
   1121                 } else {
   1122                     mEventHandler.sendMessage(
   1123                             mEventHandler.obtainMessage(MSG_REQUEST_METADATA, rcd));
   1124                 }
   1125             }
   1126         }
   1127 
   1128         public void setCurrentClientGenerationId(int clientGeneration) {
   1129             // only post messages, we can't block here
   1130             if (mEventHandler != null) {
   1131                 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
   1132                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1133                         MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
   1134             }
   1135         }
   1136 
   1137         public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
   1138             // only post messages, we can't block here
   1139             if ((mEventHandler != null) && (rcd != null)) {
   1140                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1141                         MSG_PLUG_DISPLAY, w, h, rcd));
   1142             }
   1143         }
   1144 
   1145         public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
   1146             // only post messages, we can't block here
   1147             if ((mEventHandler != null) && (rcd != null)) {
   1148                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1149                         MSG_UNPLUG_DISPLAY, rcd));
   1150             }
   1151         }
   1152 
   1153         public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
   1154             // only post messages, we can't block here
   1155             if ((mEventHandler != null) && (rcd != null)) {
   1156                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1157                         MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
   1158             }
   1159         }
   1160 
   1161         public void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync) {
   1162             // only post messages, we can't block here
   1163             if ((mEventHandler != null) && (rcd != null)) {
   1164                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1165                         MSG_DISPLAY_WANTS_POS_SYNC, wantsSync ? 1 : 0, 0/*arg2 ignored*/, rcd));
   1166             }
   1167         }
   1168 
   1169         public void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled) {
   1170             // only post messages, we can't block here
   1171             if ((mEventHandler != null) && (rcd != null)) {
   1172                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1173                         MSG_DISPLAY_ENABLE, enabled ? 1 : 0, 0/*arg2 ignored*/, rcd));
   1174             }
   1175         }
   1176 
   1177         public void seekTo(int generationId, long timeMs) {
   1178             // only post messages, we can't block here
   1179             if (mEventHandler != null) {
   1180                 mEventHandler.removeMessages(MSG_SEEK_TO);
   1181                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1182                         MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */,
   1183                         new Long(timeMs)));
   1184             }
   1185         }
   1186 
   1187         public void updateMetadata(int generationId, int key, Rating value) {
   1188             // only post messages, we can't block here
   1189             if (mEventHandler != null) {
   1190                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
   1191                         MSG_UPDATE_METADATA, generationId /* arg1 */, key /* arg2*/, value));
   1192             }
   1193         }
   1194     };
   1195 
   1196     /**
   1197      * @hide
   1198      * Default value for the unique identifier
   1199      */
   1200     public final static int RCSE_ID_UNREGISTERED = -1;
   1201     /**
   1202      * Unique identifier of the RemoteControlStackEntry in AudioService with which
   1203      * this RemoteControlClient is associated.
   1204      */
   1205     private int mRcseId = RCSE_ID_UNREGISTERED;
   1206     /**
   1207      * @hide
   1208      * To be only used by AudioManager after it has received the unique id from
   1209      * IAudioService.registerRemoteControlClient()
   1210      * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
   1211      *              this RemoteControlClient is associated.
   1212      */
   1213     public void setRcseId(int id) {
   1214         mRcseId = id;
   1215     }
   1216 
   1217     /**
   1218      * @hide
   1219      */
   1220     public int getRcseId() {
   1221         return mRcseId;
   1222     }
   1223 
   1224     private EventHandler mEventHandler;
   1225     private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
   1226     private final static int MSG_REQUEST_METADATA = 2;
   1227     private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
   1228     private final static int MSG_REQUEST_ARTWORK = 4;
   1229     private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
   1230     private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
   1231     private final static int MSG_PLUG_DISPLAY = 7;
   1232     private final static int MSG_UNPLUG_DISPLAY = 8;
   1233     private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9;
   1234     private final static int MSG_SEEK_TO = 10;
   1235     private final static int MSG_POSITION_DRIFT_CHECK = 11;
   1236     private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12;
   1237     private final static int MSG_UPDATE_METADATA = 13;
   1238     private final static int MSG_REQUEST_METADATA_ARTWORK = 14;
   1239     private final static int MSG_DISPLAY_ENABLE = 15;
   1240 
   1241     private class EventHandler extends Handler {
   1242         public EventHandler(RemoteControlClient rcc, Looper looper) {
   1243             super(looper);
   1244         }
   1245 
   1246         @Override
   1247         public void handleMessage(Message msg) {
   1248             switch(msg.what) {
   1249                 case MSG_REQUEST_PLAYBACK_STATE:
   1250                     synchronized (mCacheLock) {
   1251                         sendPlaybackState_syncCacheLock((IRemoteControlDisplay)msg.obj);
   1252                     }
   1253                     break;
   1254                 case MSG_REQUEST_METADATA:
   1255                     synchronized (mCacheLock) {
   1256                         sendMetadata_syncCacheLock((IRemoteControlDisplay)msg.obj);
   1257                     }
   1258                     break;
   1259                 case MSG_REQUEST_TRANSPORTCONTROL:
   1260                     synchronized (mCacheLock) {
   1261                         sendTransportControlInfo_syncCacheLock((IRemoteControlDisplay)msg.obj);
   1262                     }
   1263                     break;
   1264                 case MSG_REQUEST_ARTWORK:
   1265                     synchronized (mCacheLock) {
   1266                         sendArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj,
   1267                                 msg.arg1, msg.arg2);
   1268                     }
   1269                     break;
   1270                 case MSG_REQUEST_METADATA_ARTWORK:
   1271                     synchronized (mCacheLock) {
   1272                         sendMetadataWithArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj,
   1273                                 msg.arg1, msg.arg2);
   1274                     }
   1275                     break;
   1276                 case MSG_NEW_INTERNAL_CLIENT_GEN:
   1277                     onNewInternalClientGen(msg.arg1);
   1278                     break;
   1279                 case MSG_NEW_CURRENT_CLIENT_GEN:
   1280                     onNewCurrentClientGen(msg.arg1);
   1281                     break;
   1282                 case MSG_PLUG_DISPLAY:
   1283                     onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
   1284                     break;
   1285                 case MSG_UNPLUG_DISPLAY:
   1286                     onUnplugDisplay((IRemoteControlDisplay)msg.obj);
   1287                     break;
   1288                 case MSG_UPDATE_DISPLAY_ARTWORK_SIZE:
   1289                     onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
   1290                     break;
   1291                 case MSG_SEEK_TO:
   1292                     onSeekTo(msg.arg1, ((Long)msg.obj).longValue());
   1293                     break;
   1294                 case MSG_POSITION_DRIFT_CHECK:
   1295                     onPositionDriftCheck();
   1296                     break;
   1297                 case MSG_DISPLAY_WANTS_POS_SYNC:
   1298                     onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
   1299                     break;
   1300                 case MSG_UPDATE_METADATA:
   1301                     onUpdateMetadata(msg.arg1, msg.arg2, msg.obj);
   1302                     break;
   1303                 case MSG_DISPLAY_ENABLE:
   1304                     onDisplayEnable((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
   1305                     break;
   1306                 default:
   1307                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
   1308             }
   1309         }
   1310     }
   1311 
   1312     //===========================================================
   1313     // Communication with the IRemoteControlDisplay (the displays known to the system)
   1314 
   1315     private void sendPlaybackState_syncCacheLock(IRemoteControlDisplay target) {
   1316         if (mCurrentClientGenId == mInternalClientGenId) {
   1317             if (target != null) {
   1318                 try {
   1319                     target.setPlaybackState(mInternalClientGenId,
   1320                             mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
   1321                             mPlaybackSpeed);
   1322                 } catch (RemoteException e) {
   1323                     Log.e(TAG, "Error in setPlaybackState() for dead display " + target, e);
   1324                 }
   1325                 return;
   1326             }
   1327             // target == null implies all displays must be updated
   1328             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1329             while (displayIterator.hasNext()) {
   1330                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1331                 if (di.mEnabled) {
   1332                     try {
   1333                         di.mRcDisplay.setPlaybackState(mInternalClientGenId,
   1334                                 mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
   1335                                 mPlaybackSpeed);
   1336                     } catch (RemoteException e) {
   1337                         Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
   1338                         displayIterator.remove();
   1339                     }
   1340                 }
   1341             }
   1342         }
   1343     }
   1344 
   1345     private void sendMetadata_syncCacheLock(IRemoteControlDisplay target) {
   1346         if (mCurrentClientGenId == mInternalClientGenId) {
   1347             if (target != null) {
   1348                 try {
   1349                     target.setMetadata(mInternalClientGenId, mMetadata);
   1350                 } catch (RemoteException e) {
   1351                     Log.e(TAG, "Error in setMetadata() for dead display " + target, e);
   1352                 }
   1353                 return;
   1354             }
   1355             // target == null implies all displays must be updated
   1356             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1357             while (displayIterator.hasNext()) {
   1358                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1359                 if (di.mEnabled) {
   1360                     try {
   1361                         di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
   1362                     } catch (RemoteException e) {
   1363                         Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
   1364                         displayIterator.remove();
   1365                     }
   1366                 }
   1367             }
   1368         }
   1369     }
   1370 
   1371     private void sendTransportControlInfo_syncCacheLock(IRemoteControlDisplay target) {
   1372         if (mCurrentClientGenId == mInternalClientGenId) {
   1373             if (target != null) {
   1374                 try {
   1375                     target.setTransportControlInfo(mInternalClientGenId,
   1376                             mTransportControlFlags, mPlaybackPositionCapabilities);
   1377                 } catch (RemoteException e) {
   1378                     Log.e(TAG, "Error in setTransportControlFlags() for dead display " + target,
   1379                             e);
   1380                 }
   1381                 return;
   1382             }
   1383             // target == null implies all displays must be updated
   1384             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1385             while (displayIterator.hasNext()) {
   1386                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1387                 if (di.mEnabled) {
   1388                     try {
   1389                         di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
   1390                                 mTransportControlFlags, mPlaybackPositionCapabilities);
   1391                     } catch (RemoteException e) {
   1392                         Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
   1393                                 e);
   1394                         displayIterator.remove();
   1395                     }
   1396                 }
   1397             }
   1398         }
   1399     }
   1400 
   1401     private void sendArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) {
   1402         // FIXME modify to cache all requested sizes?
   1403         if (mCurrentClientGenId == mInternalClientGenId) {
   1404             if (target != null) {
   1405                 final DisplayInfoForClient di = new DisplayInfoForClient(target, w, h);
   1406                 sendArtworkToDisplay(di);
   1407                 return;
   1408             }
   1409             // target == null implies all displays must be updated
   1410             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1411             while (displayIterator.hasNext()) {
   1412                 if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
   1413                     displayIterator.remove();
   1414                 }
   1415             }
   1416         }
   1417     }
   1418 
   1419     /**
   1420      * Send artwork to an IRemoteControlDisplay.
   1421      * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its
   1422      *    dimension requirements.
   1423      * @return false if there was an error communicating with the IRemoteControlDisplay.
   1424      */
   1425     private boolean sendArtworkToDisplay(DisplayInfoForClient di) {
   1426         if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
   1427             Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
   1428                     di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
   1429             try {
   1430                 di.mRcDisplay.setArtwork(mInternalClientGenId, artwork);
   1431             } catch (RemoteException e) {
   1432                 Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e);
   1433                 return false;
   1434             }
   1435         }
   1436         return true;
   1437     }
   1438 
   1439     private void sendMetadataWithArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) {
   1440         // FIXME modify to cache all requested sizes?
   1441         if (mCurrentClientGenId == mInternalClientGenId) {
   1442             if (target != null) {
   1443                 try {
   1444                     if ((w > 0) && (h > 0)) {
   1445                         Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, w, h);
   1446                         target.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
   1447                     } else {
   1448                         target.setMetadata(mInternalClientGenId, mMetadata);
   1449                     }
   1450                 } catch (RemoteException e) {
   1451                     Log.e(TAG, "Error in set(All)Metadata() for dead display " + target, e);
   1452                 }
   1453                 return;
   1454             }
   1455             // target == null implies all displays must be updated
   1456             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1457             while (displayIterator.hasNext()) {
   1458                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1459                 try {
   1460                     if (di.mEnabled) {
   1461                         if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
   1462                             Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
   1463                                     di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
   1464                             di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
   1465                         } else {
   1466                             di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
   1467                         }
   1468                     }
   1469                 } catch (RemoteException e) {
   1470                     Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
   1471                     displayIterator.remove();
   1472                 }
   1473             }
   1474         }
   1475     }
   1476 
   1477     //===========================================================
   1478     // Communication with AudioService
   1479 
   1480     private static IAudioService sService;
   1481 
   1482     private static IAudioService getService()
   1483     {
   1484         if (sService != null) {
   1485             return sService;
   1486         }
   1487         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
   1488         sService = IAudioService.Stub.asInterface(b);
   1489         return sService;
   1490     }
   1491 
   1492     private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
   1493         if (mRcseId == RCSE_ID_UNREGISTERED) {
   1494             return;
   1495         }
   1496         //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
   1497         IAudioService service = getService();
   1498         try {
   1499             service.setPlaybackInfoForRcc(mRcseId, what, value);
   1500         } catch (RemoteException e) {
   1501             Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e);
   1502         }
   1503     }
   1504 
   1505     private void sendAudioServiceNewPlaybackState_syncCacheLock() {
   1506         if (mRcseId == RCSE_ID_UNREGISTERED) {
   1507             return;
   1508         }
   1509         IAudioService service = getService();
   1510         try {
   1511             service.setPlaybackStateForRcc(mRcseId,
   1512                     mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed);
   1513         } catch (RemoteException e) {
   1514             Log.e(TAG, "Dead object in setPlaybackStateForRcc", e);
   1515         }
   1516     }
   1517 
   1518     //===========================================================
   1519     // Message handlers
   1520 
   1521     private void onNewInternalClientGen(int clientGeneration) {
   1522         synchronized (mCacheLock) {
   1523             // this remote control client is told it is the "focused" one:
   1524             // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
   1525             mInternalClientGenId = clientGeneration;
   1526         }
   1527     }
   1528 
   1529     private void onNewCurrentClientGen(int clientGeneration) {
   1530         synchronized (mCacheLock) {
   1531             mCurrentClientGenId = clientGeneration;
   1532         }
   1533     }
   1534 
   1535     /** pre-condition rcd != null */
   1536     private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) {
   1537         synchronized(mCacheLock) {
   1538             // do we have this display already?
   1539             boolean displayKnown = false;
   1540             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1541             while (displayIterator.hasNext() && !displayKnown) {
   1542                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1543                 displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
   1544                 if (displayKnown) {
   1545                     // this display was known but the change in artwork size will cause the
   1546                     // artwork to be refreshed
   1547                     if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
   1548                         di.mArtworkExpectedWidth = w;
   1549                         di.mArtworkExpectedHeight = h;
   1550                         if (!sendArtworkToDisplay(di)) {
   1551                             displayIterator.remove();
   1552                         }
   1553                     }
   1554                 }
   1555             }
   1556             if (!displayKnown) {
   1557                 mRcDisplays.add(new DisplayInfoForClient(rcd, w, h));
   1558             }
   1559         }
   1560     }
   1561 
   1562     /** pre-condition rcd != null */
   1563     private void onUnplugDisplay(IRemoteControlDisplay rcd) {
   1564         synchronized(mCacheLock) {
   1565             Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1566             while (displayIterator.hasNext()) {
   1567                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1568                 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
   1569                     displayIterator.remove();
   1570                     break;
   1571                 }
   1572             }
   1573             // list of RCDs has changed, reevaluate whether position check is still needed
   1574             boolean oldNeedsPositionSync = mNeedsPositionSync;
   1575             boolean newNeedsPositionSync = false;
   1576             displayIterator = mRcDisplays.iterator();
   1577             while (displayIterator.hasNext()) {
   1578                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1579                 if (di.mWantsPositionSync) {
   1580                     newNeedsPositionSync = true;
   1581                     break;
   1582                 }
   1583             }
   1584             mNeedsPositionSync = newNeedsPositionSync;
   1585             if (oldNeedsPositionSync != mNeedsPositionSync) {
   1586                 // update needed?
   1587                 initiateCheckForDrift_syncCacheLock();
   1588             }
   1589         }
   1590     }
   1591 
   1592     /** pre-condition rcd != null */
   1593     private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) {
   1594         synchronized(mCacheLock) {
   1595             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1596             while (displayIterator.hasNext()) {
   1597                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1598                 if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
   1599                         ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
   1600                     di.mArtworkExpectedWidth = w;
   1601                     di.mArtworkExpectedHeight = h;
   1602                     if (di.mEnabled) {
   1603                         if (!sendArtworkToDisplay(di)) {
   1604                             displayIterator.remove();
   1605                         }
   1606                     }
   1607                     break;
   1608                 }
   1609             }
   1610         }
   1611     }
   1612 
   1613     /** pre-condition rcd != null */
   1614     private void onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync) {
   1615         synchronized(mCacheLock) {
   1616             boolean oldNeedsPositionSync = mNeedsPositionSync;
   1617             boolean newNeedsPositionSync = false;
   1618             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1619             // go through the list of RCDs and for each entry, check both whether this is the RCD
   1620             //  that gets upated, and whether the list has one entry that wants position sync
   1621             while (displayIterator.hasNext()) {
   1622                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1623                 if (di.mEnabled) {
   1624                     if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
   1625                         di.mWantsPositionSync = wantsSync;
   1626                     }
   1627                     if (di.mWantsPositionSync) {
   1628                         newNeedsPositionSync = true;
   1629                     }
   1630                 }
   1631             }
   1632             mNeedsPositionSync = newNeedsPositionSync;
   1633             if (oldNeedsPositionSync != mNeedsPositionSync) {
   1634                 // update needed?
   1635                 initiateCheckForDrift_syncCacheLock();
   1636             }
   1637         }
   1638     }
   1639 
   1640     /** pre-condition rcd != null */
   1641     private void onDisplayEnable(IRemoteControlDisplay rcd, boolean enable) {
   1642         synchronized(mCacheLock) {
   1643             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
   1644             while (displayIterator.hasNext()) {
   1645                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
   1646                 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
   1647                     di.mEnabled = enable;
   1648                 }
   1649             }
   1650         }
   1651     }
   1652 
   1653     private void onSeekTo(int generationId, long timeMs) {
   1654         synchronized (mCacheLock) {
   1655             if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
   1656                 mPositionUpdateListener.onPlaybackPositionUpdate(timeMs);
   1657             }
   1658         }
   1659     }
   1660 
   1661     private void onUpdateMetadata(int generationId, int key, Object value) {
   1662         synchronized (mCacheLock) {
   1663             if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
   1664                 mMetadataUpdateListener.onMetadataUpdate(key, value);
   1665             }
   1666         }
   1667     }
   1668 
   1669     //===========================================================
   1670     // Internal utilities
   1671 
   1672     /**
   1673      * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
   1674      * If the bitmap fits, then do nothing and return the original.
   1675      *
   1676      * @param bitmap
   1677      * @param maxWidth
   1678      * @param maxHeight
   1679      * @return
   1680      */
   1681 
   1682     private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
   1683         if (bitmap != null) {
   1684             final int width = bitmap.getWidth();
   1685             final int height = bitmap.getHeight();
   1686             if (width > maxWidth || height > maxHeight) {
   1687                 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
   1688                 int newWidth = Math.round(scale * width);
   1689                 int newHeight = Math.round(scale * height);
   1690                 Bitmap.Config newConfig = bitmap.getConfig();
   1691                 if (newConfig == null) {
   1692                     newConfig = Bitmap.Config.ARGB_8888;
   1693                 }
   1694                 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
   1695                 Canvas canvas = new Canvas(outBitmap);
   1696                 Paint paint = new Paint();
   1697                 paint.setAntiAlias(true);
   1698                 paint.setFilterBitmap(true);
   1699                 canvas.drawBitmap(bitmap, null,
   1700                         new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
   1701                 bitmap = outBitmap;
   1702             }
   1703         }
   1704         return bitmap;
   1705     }
   1706 
   1707 
   1708     /**
   1709      * Returns whether, for the given playback state, the playback position is expected to
   1710      * be changing.
   1711      * @param playstate the playback state to evaluate
   1712      * @return true during any form of playback, false if it's not playing anything while in this
   1713      *     playback state
   1714      */
   1715     static boolean playbackPositionShouldMove(int playstate) {
   1716         switch(playstate) {
   1717             case PLAYSTATE_STOPPED:
   1718             case PLAYSTATE_PAUSED:
   1719             case PLAYSTATE_BUFFERING:
   1720             case PLAYSTATE_ERROR:
   1721             case PLAYSTATE_SKIPPING_FORWARDS:
   1722             case PLAYSTATE_SKIPPING_BACKWARDS:
   1723                 return false;
   1724             case PLAYSTATE_PLAYING:
   1725             case PLAYSTATE_FAST_FORWARDING:
   1726             case PLAYSTATE_REWINDING:
   1727             default:
   1728                 return true;
   1729         }
   1730     }
   1731 
   1732     /**
   1733      * Period for playback position drift checks, 15s when playing at 1x or slower.
   1734      */
   1735     private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
   1736     /**
   1737      * Minimum period for playback position drift checks, never more often when every 2s, when
   1738      * fast forwarding or rewinding.
   1739      */
   1740     private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
   1741     /**
   1742      * The value above which the difference between client-reported playback position and
   1743      * estimated position is considered a drift.
   1744      */
   1745     private final static long POSITION_DRIFT_MAX_MS = 500;
   1746     /**
   1747      * Compute the period at which the estimated playback position should be compared against the
   1748      * actual playback position. Is a funciton of playback speed.
   1749      * @param speed 1.0f is normal playback speed
   1750      * @return the period in ms
   1751      */
   1752     private static long getCheckPeriodFromSpeed(float speed) {
   1753         if (Math.abs(speed) <= 1.0f) {
   1754             return POSITION_REFRESH_PERIOD_PLAYING_MS;
   1755         } else {
   1756             return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)),
   1757                     POSITION_REFRESH_PERIOD_MIN_MS);
   1758         }
   1759     }
   1760 }
   1761