• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.app.PendingIntent;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.RectF;
27 import android.media.MediaMetadataRetriever;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.SystemClock;
36 import android.util.Log;
37 
38 import java.lang.IllegalArgumentException;
39 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      */
RemoteControlClient(PendingIntent mediaButtonIntent)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      */
RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper)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
MetadataEditor()422         private MetadataEditor() { }
423         /**
424          * @hide
425          */
clone()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          */
putString(int key, String value)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          */
putLong(int key, long value)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          */
putBitmap(int key, Bitmap bitmap)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          */
clear()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          */
apply()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      */
editMetadata(boolean startEmpty)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      */
setPlaybackState(int state)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      */
setPlaybackState(int state, long timeInMs, float playbackSpeed)638     public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
639         setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
640     }
641 
setPlaybackStateInt(int state, long timeInMs, float playbackSpeed, boolean hasPosition)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 
initiateCheckForDrift_syncCacheLock()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 
onPositionDriftCheck()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      */
setTransportControlFlags(int transportControlFlags)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          */
onPlaybackPositionUpdate(long newPositionMs)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          */
onGetPlaybackPosition()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      */
setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l)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      */
setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l)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      */
setPlaybackInformation(int what, int value)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      */
getIntPlaybackInformation(int what)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 
DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h)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      */
getRcMediaIntent()1078     public PendingIntent getRcMediaIntent() {
1079         return mRcMediaIntent;
1080     }
1081     /**
1082      * @hide
1083      * Accessor to IRemoteControlClient
1084      */
getIRemoteControlClient()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      */
setRcseId(int id)1185     public void setRcseId(int id) {
1186         mRcseId = id;
1187     }
1188 
1189     /**
1190      * @hide
1191      */
getRcseId()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 {
EventHandler(RemoteControlClient rcc, Looper looper)1211         public EventHandler(RemoteControlClient rcc, Looper looper) {
1212             super(looper);
1213         }
1214 
1215         @Override
handleMessage(Message msg)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 
sendPlaybackState_syncCacheLock()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 
sendMetadata_syncCacheLock()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 
sendTransportControlInfo_syncCacheLock()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 
sendArtwork_syncCacheLock()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      */
sendArtworkToDisplay(DisplayInfoForClient di)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 
sendMetadataWithArtwork_syncCacheLock()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 
getService()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 
sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value)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 
sendAudioServiceNewPlaybackState_syncCacheLock()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 
onNewInternalClientGen(int clientGeneration)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 
onNewCurrentClientGen(int clientGeneration)1426     private void onNewCurrentClientGen(int clientGeneration) {
1427         synchronized (mCacheLock) {
1428             mCurrentClientGenId = clientGeneration;
1429         }
1430     }
1431 
1432     /** pre-condition rcd != null */
onPlugDisplay(IRemoteControlDisplay rcd, int w, int h)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 */
onUnplugDisplay(IRemoteControlDisplay rcd)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 */
onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h)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 */
onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync)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 
onSeekTo(int generationId, long timeMs)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 
scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight)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      */
validTypeForKey(int key, int[] validKeys)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      */
playbackPositionShouldMove(int playstate)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      */
getCheckPeriodFromSpeed(float speed)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