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