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.Intent;
22 import android.graphics.Bitmap;
23 import android.media.session.MediaSessionLegacyHelper;
24 import android.media.session.PlaybackState;
25 import android.media.session.MediaSession;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.SystemClock;
31 import android.util.Log;
32 
33 import java.lang.IllegalArgumentException;
34 
35 /**
36  * RemoteControlClient enables exposing information meant to be consumed by remote controls
37  * capable of displaying metadata, artwork and media transport control buttons.
38  *
39  * <p>A remote control client object is associated with a media button event receiver. This
40  * event receiver must have been previously registered with
41  * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
42  * RemoteControlClient can be registered through
43  * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
44  *
45  * <p>Here is an example of creating a RemoteControlClient instance after registering a media
46  * button event receiver:
47  * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
48  * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
49  * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
50  * // build the PendingIntent for the remote control client
51  * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
52  * mediaButtonIntent.setComponent(myEventReceiver);
53  * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
54  * // create and register the remote control client
55  * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
56  * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre>
57  *
58  * @deprecated Use {@link MediaSession} instead.
59  */
60 @Deprecated public class RemoteControlClient
61 {
62     private final static String TAG = "RemoteControlClient";
63     private final static boolean DEBUG = false;
64 
65     /**
66      * Playback state of a RemoteControlClient which is stopped.
67      *
68      * @see #setPlaybackState(int)
69      */
70     public final static int PLAYSTATE_STOPPED            = 1;
71     /**
72      * Playback state of a RemoteControlClient which is paused.
73      *
74      * @see #setPlaybackState(int)
75      */
76     public final static int PLAYSTATE_PAUSED             = 2;
77     /**
78      * Playback state of a RemoteControlClient which is playing media.
79      *
80      * @see #setPlaybackState(int)
81      */
82     public final static int PLAYSTATE_PLAYING            = 3;
83     /**
84      * Playback state of a RemoteControlClient which is fast forwarding in the media
85      *    it is currently playing.
86      *
87      * @see #setPlaybackState(int)
88      */
89     public final static int PLAYSTATE_FAST_FORWARDING    = 4;
90     /**
91      * Playback state of a RemoteControlClient which is fast rewinding in the media
92      *    it is currently playing.
93      *
94      * @see #setPlaybackState(int)
95      */
96     public final static int PLAYSTATE_REWINDING          = 5;
97     /**
98      * Playback state of a RemoteControlClient which is skipping to the next
99      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
100      *
101      * @see #setPlaybackState(int)
102      */
103     public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
104     /**
105      * Playback state of a RemoteControlClient which is skipping back to the previous
106      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
107      *
108      * @see #setPlaybackState(int)
109      */
110     public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
111     /**
112      * Playback state of a RemoteControlClient which is buffering data to play before it can
113      *    start or resume playback.
114      *
115      * @see #setPlaybackState(int)
116      */
117     public final static int PLAYSTATE_BUFFERING          = 8;
118     /**
119      * Playback state of a RemoteControlClient which cannot perform any playback related
120      *    operation because of an internal error. Examples of such situations are no network
121      *    connectivity when attempting to stream data from a server, or expired user credentials
122      *    when trying to play subscription-based content.
123      *
124      * @see #setPlaybackState(int)
125      */
126     public final static int PLAYSTATE_ERROR              = 9;
127     /**
128      * @hide
129      * The value of a playback state when none has been declared.
130      * Intentionally hidden as an application shouldn't set such a playback state value.
131      */
132     public final static int PLAYSTATE_NONE               = 0;
133 
134     /**
135      * @hide
136      * The default playback type, "local", indicating the presentation of the media is happening on
137      * the same device (e.g. a phone, a tablet) as where it is controlled from.
138      */
139     public final static int PLAYBACK_TYPE_LOCAL = 0;
140     /**
141      * @hide
142      * A playback type indicating the presentation of the media is happening on
143      * a different device (i.e. the remote device) than where it is controlled from.
144      */
145     public final static int PLAYBACK_TYPE_REMOTE = 1;
146     private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
147     private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
148     /**
149      * @hide
150      * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
151      * from this object. An example of fixed playback volume is a remote player, playing over HDMI
152      * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
153      * source.
154      * @see #PLAYBACKINFO_VOLUME_HANDLING.
155      */
156     public final static int PLAYBACK_VOLUME_FIXED = 0;
157     /**
158      * @hide
159      * Playback information indicating the playback volume is variable and can be controlled from
160      * this object.
161      * @see #PLAYBACKINFO_VOLUME_HANDLING.
162      */
163     public final static int PLAYBACK_VOLUME_VARIABLE = 1;
164     /**
165      * @hide (to be un-hidden)
166      * The playback information value indicating the value of a given information type is invalid.
167      * @see #PLAYBACKINFO_VOLUME_HANDLING.
168      */
169     public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
170 
171     /**
172      * @hide
173      * An unknown or invalid playback position value.
174      */
175     public final static long PLAYBACK_POSITION_INVALID = -1;
176     /**
177      * @hide
178      * An invalid playback position value associated with the use of {@link #setPlaybackState(int)}
179      * used to indicate that playback position will remain unknown.
180      */
181     public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L;
182     /**
183      * @hide
184      * The default playback speed, 1x.
185      */
186     public final static float PLAYBACK_SPEED_1X = 1.0f;
187 
188     //==========================================
189     // Public keys for playback information
190     /**
191      * @hide
192      * Playback information that defines the type of playback associated with this
193      * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
194      */
195     public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
196     /**
197      * @hide
198      * Playback information that defines at what volume the playback associated with this
199      * RemoteControlClient is performed. This information is only used when the playback type is not
200      * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
201      */
202     public final static int PLAYBACKINFO_VOLUME = 2;
203     /**
204      * @hide
205      * Playback information that defines the maximum volume volume value that is supported
206      * by the playback associated with this RemoteControlClient. This information is only used
207      * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
208      */
209     public final static int PLAYBACKINFO_VOLUME_MAX = 3;
210     /**
211      * @hide
212      * Playback information that defines how volume is handled for the presentation of the media.
213      * @see #PLAYBACK_VOLUME_FIXED
214      * @see #PLAYBACK_VOLUME_VARIABLE
215      */
216     public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
217     /**
218      * @hide
219      * Playback information that defines over what stream type the media is presented.
220      */
221     public final static int PLAYBACKINFO_USES_STREAM = 5;
222 
223     //==========================================
224     // Public flags for the supported transport control capabilities
225     /**
226      * Flag indicating a RemoteControlClient makes use of the "previous" media key.
227      *
228      * @see #setTransportControlFlags(int)
229      * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
230      */
231     public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
232     /**
233      * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
234      *
235      * @see #setTransportControlFlags(int)
236      * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
237      */
238     public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
239     /**
240      * Flag indicating a RemoteControlClient makes use of the "play" media key.
241      *
242      * @see #setTransportControlFlags(int)
243      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
244      */
245     public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
246     /**
247      * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
248      *
249      * @see #setTransportControlFlags(int)
250      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
251      */
252     public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
253     /**
254      * Flag indicating a RemoteControlClient makes use of the "pause" media key.
255      *
256      * @see #setTransportControlFlags(int)
257      * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
258      */
259     public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
260     /**
261      * Flag indicating a RemoteControlClient makes use of the "stop" media key.
262      *
263      * @see #setTransportControlFlags(int)
264      * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
265      */
266     public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
267     /**
268      * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
269      *
270      * @see #setTransportControlFlags(int)
271      * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
272      */
273     public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
274     /**
275      * Flag indicating a RemoteControlClient makes use of the "next" media key.
276      *
277      * @see #setTransportControlFlags(int)
278      * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
279      */
280     public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
281     /**
282      * Flag indicating a RemoteControlClient can receive changes in the media playback position
283      * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set
284      * in order for components that display the RemoteControlClient information, to display and
285      * let the user control media playback position.
286      * @see #setTransportControlFlags(int)
287      * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener)
288      * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener)
289      */
290     public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
291     /**
292      * Flag indicating a RemoteControlClient supports ratings.
293      * This flag must be set in order for components that display the RemoteControlClient
294      * information, to display ratings information, and, if ratings are declared editable
295      * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the
296      * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate
297      * the media, with values being received through the interface set with
298      * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}.
299      * @see #setTransportControlFlags(int)
300      */
301     public final static int FLAG_KEY_MEDIA_RATING = 1 << 9;
302 
303     /**
304      * @hide
305      * The flags for when no media keys are declared supported.
306      * Intentionally hidden as an application shouldn't set the transport control flags
307      *     to this value.
308      */
309     public final static int FLAGS_KEY_MEDIA_NONE = 0;
310 
311     /**
312      * @hide
313      * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
314      */
315     public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
316     /**
317      * @hide
318      * Flag used to signal that the transport control buttons supported by the
319      *     RemoteControlClient are requested.
320      * This can for instance happen when playback is at the end of a playlist, and the "next"
321      * operation is not supported anymore.
322      */
323     public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
324     /**
325      * @hide
326      * Flag used to signal that the playback state of the RemoteControlClient is requested.
327      */
328     public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
329     /**
330      * @hide
331      * Flag used to signal that the album art for the RemoteControlClient is requested.
332      */
333     public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
334 
335     private MediaSession mSession;
336 
337     /**
338      * Class constructor.
339      * @param mediaButtonIntent The intent that will be sent for the media button events sent
340      *     by remote controls.
341      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
342      *     action, and have a component that will handle the intent (set with
343      *     {@link Intent#setComponent(ComponentName)}) registered with
344      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
345      *     before this new RemoteControlClient can itself be registered with
346      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
347      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
348      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
349      */
RemoteControlClient(PendingIntent mediaButtonIntent)350     public RemoteControlClient(PendingIntent mediaButtonIntent) {
351         mRcMediaIntent = mediaButtonIntent;
352 
353         Looper looper;
354         if ((looper = Looper.myLooper()) != null) {
355             mEventHandler = new EventHandler(this, looper);
356         } else if ((looper = Looper.getMainLooper()) != null) {
357             mEventHandler = new EventHandler(this, looper);
358         } else {
359             mEventHandler = null;
360             Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
361         }
362     }
363 
364     /**
365      * Class constructor for a remote control client whose internal event handling
366      * happens on a user-provided Looper.
367      * @param mediaButtonIntent The intent that will be sent for the media button events sent
368      *     by remote controls.
369      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
370      *     action, and have a component that will handle the intent (set with
371      *     {@link Intent#setComponent(ComponentName)}) registered with
372      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
373      *     before this new RemoteControlClient can itself be registered with
374      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
375      * @param looper The Looper running the event loop.
376      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
377      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
378      */
RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper)379     public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
380         mRcMediaIntent = mediaButtonIntent;
381 
382         mEventHandler = new EventHandler(this, looper);
383     }
384 
385     /**
386      * @hide
387      */
registerWithSession(MediaSessionLegacyHelper helper)388     public void registerWithSession(MediaSessionLegacyHelper helper) {
389         helper.addRccListener(mRcMediaIntent, mTransportListener);
390         mSession = helper.getSession(mRcMediaIntent);
391         setTransportControlFlags(mTransportControlFlags);
392     }
393 
394     /**
395      * @hide
396      */
unregisterWithSession(MediaSessionLegacyHelper helper)397     public void unregisterWithSession(MediaSessionLegacyHelper helper) {
398         helper.removeRccListener(mRcMediaIntent);
399         mSession = null;
400     }
401 
402     /**
403      * Get a {@link MediaSession} associated with this RCC. It will only have a
404      * session while it is registered with
405      * {@link AudioManager#registerRemoteControlClient}. The session returned
406      * should not be modified directly by the application but may be used with
407      * other APIs that require a session.
408      *
409      * @return A media session object or null.
410      */
getMediaSession()411     public MediaSession getMediaSession() {
412         return mSession;
413     }
414 
415     /**
416      * Class used to modify metadata in a {@link RemoteControlClient} object.
417      * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
418      * on which you set the metadata for the RemoteControlClient instance. Once all the information
419      * has been set, use {@link #apply()} to make it the new metadata that should be displayed
420      * for the associated client. Once the metadata has been "applied", you cannot reuse this
421      * instance of the MetadataEditor.
422      *
423      * @deprecated Use {@link MediaMetadata} and {@link MediaSession} instead.
424      */
425     @Deprecated public class MetadataEditor extends MediaMetadataEditor {
426 
427         // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
MetadataEditor()428         private MetadataEditor() { }
429         /**
430          * @hide
431          */
clone()432         public Object clone() throws CloneNotSupportedException {
433             throw new CloneNotSupportedException();
434         }
435 
436         /**
437          * The metadata key for the content artwork / album art.
438          */
439         public final static int BITMAP_KEY_ARTWORK = 100;
440 
441         /**
442          * @hide
443          * TODO(jmtrivi) have lockscreen move to the new key name and remove
444          */
445         public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
446 
447         /**
448          * Adds textual information to be displayed.
449          * Note that none of the information added after {@link #apply()} has been called,
450          * will be displayed.
451          * @param key The identifier of a the metadata field to set. Valid values are
452          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
453          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
454          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
455          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
456          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
457          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
458          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
459          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
460          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
461          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
462          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
463          * @param value The text for the given key, or {@code null} to signify there is no valid
464          *      information for the field.
465          * @return Returns a reference to the same MetadataEditor object, so you can chain put
466          *      calls together.
467          */
putString(int key, String value)468         public synchronized MetadataEditor putString(int key, String value)
469                 throws IllegalArgumentException {
470             super.putString(key, value);
471             if (mMetadataBuilder != null) {
472                 // MediaMetadata supports all the same fields as MetadataEditor
473                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
474                 // But just in case, don't add things we don't understand
475                 if (metadataKey != null) {
476                     mMetadataBuilder.putText(metadataKey, value);
477                 }
478             }
479 
480             return this;
481         }
482 
483         /**
484          * Adds numerical information to be displayed.
485          * Note that none of the information added after {@link #apply()} has been called,
486          * will be displayed.
487          * @param key the identifier of a the metadata field to set. Valid values are
488          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
489          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
490          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
491          *      expressed in milliseconds),
492          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
493          * @param value The long value for the given key
494          * @return Returns a reference to the same MetadataEditor object, so you can chain put
495          *      calls together.
496          * @throws IllegalArgumentException
497          */
putLong(int key, long value)498         public synchronized MetadataEditor putLong(int key, long value)
499                 throws IllegalArgumentException {
500             super.putLong(key, value);
501             if (mMetadataBuilder != null) {
502                 // MediaMetadata supports all the same fields as MetadataEditor
503                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
504                 // But just in case, don't add things we don't understand
505                 if (metadataKey != null) {
506                     mMetadataBuilder.putLong(metadataKey, value);
507                 }
508             }
509             return this;
510         }
511 
512         /**
513          * Sets the album / artwork picture to be displayed on the remote control.
514          * @param key the identifier of the bitmap to set. The only valid value is
515          *      {@link #BITMAP_KEY_ARTWORK}
516          * @param bitmap The bitmap for the artwork, or null if there isn't any.
517          * @return Returns a reference to the same MetadataEditor object, so you can chain put
518          *      calls together.
519          * @throws IllegalArgumentException
520          * @see android.graphics.Bitmap
521          */
522         @Override
putBitmap(int key, Bitmap bitmap)523         public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
524                 throws IllegalArgumentException {
525             super.putBitmap(key, bitmap);
526             if (mMetadataBuilder != null) {
527                 // MediaMetadata supports all the same fields as MetadataEditor
528                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
529                 // But just in case, don't add things we don't understand
530                 if (metadataKey != null) {
531                     mMetadataBuilder.putBitmap(metadataKey, bitmap);
532                 }
533             }
534             return this;
535         }
536 
537         @Override
putObject(int key, Object object)538         public synchronized MetadataEditor putObject(int key, Object object)
539                 throws IllegalArgumentException {
540             super.putObject(key, object);
541             if (mMetadataBuilder != null &&
542                     (key == MediaMetadataEditor.RATING_KEY_BY_USER ||
543                     key == MediaMetadataEditor.RATING_KEY_BY_OTHERS)) {
544                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
545                 if (metadataKey != null) {
546                     mMetadataBuilder.putRating(metadataKey, (Rating) object);
547                 }
548             }
549             return this;
550         }
551 
552         /**
553          * Clears all the metadata that has been set since the MetadataEditor instance was created
554          * (with {@link RemoteControlClient#editMetadata(boolean)}).
555          * Note that clearing the metadata doesn't reset the editable keys
556          * (use {@link MediaMetadataEditor#removeEditableKeys()} instead).
557          */
558         @Override
clear()559         public synchronized void clear() {
560             super.clear();
561         }
562 
563         /**
564          * Associates all the metadata that has been set since the MetadataEditor instance was
565          *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
566          *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
567          *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
568          */
apply()569         public synchronized void apply() {
570             if (mApplied) {
571                 Log.e(TAG, "Can't apply a previously applied MetadataEditor");
572                 return;
573             }
574             synchronized (mCacheLock) {
575                 // Still build the old metadata so when creating a new editor
576                 // you get the expected values.
577                 // assign the edited data
578                 mMetadata = new Bundle(mEditorMetadata);
579                 // add the information about editable keys
580                 mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys);
581                 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
582                     mOriginalArtwork.recycle();
583                 }
584                 mOriginalArtwork = mEditorArtwork;
585                 mEditorArtwork = null;
586 
587                 // USE_SESSIONS
588                 if (mSession != null && mMetadataBuilder != null) {
589                     mMediaMetadata = mMetadataBuilder.build();
590                     mSession.setMetadata(mMediaMetadata);
591                 }
592                 mApplied = true;
593             }
594         }
595     }
596 
597     /**
598      * Creates a {@link MetadataEditor}.
599      * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
600      *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
601      * @return a new MetadataEditor instance.
602      */
editMetadata(boolean startEmpty)603     public MetadataEditor editMetadata(boolean startEmpty) {
604         MetadataEditor editor = new MetadataEditor();
605         if (startEmpty) {
606             editor.mEditorMetadata = new Bundle();
607             editor.mEditorArtwork = null;
608             editor.mMetadataChanged = true;
609             editor.mArtworkChanged = true;
610             editor.mEditableKeys = 0;
611         } else {
612             editor.mEditorMetadata = new Bundle(mMetadata);
613             editor.mEditorArtwork = mOriginalArtwork;
614             editor.mMetadataChanged = false;
615             editor.mArtworkChanged = false;
616         }
617         // USE_SESSIONS
618         if (startEmpty || mMediaMetadata == null) {
619             editor.mMetadataBuilder = new MediaMetadata.Builder();
620         } else {
621             editor.mMetadataBuilder = new MediaMetadata.Builder(mMediaMetadata);
622         }
623         return editor;
624     }
625 
626     /**
627      * Sets the current playback state.
628      * @param state The current playback state, one of the following values:
629      *       {@link #PLAYSTATE_STOPPED},
630      *       {@link #PLAYSTATE_PAUSED},
631      *       {@link #PLAYSTATE_PLAYING},
632      *       {@link #PLAYSTATE_FAST_FORWARDING},
633      *       {@link #PLAYSTATE_REWINDING},
634      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
635      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
636      *       {@link #PLAYSTATE_BUFFERING},
637      *       {@link #PLAYSTATE_ERROR}.
638      */
setPlaybackState(int state)639     public void setPlaybackState(int state) {
640         setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X,
641                 false /* legacy API, converting to method with position and speed */);
642     }
643 
644     /**
645      * Sets the current playback state and the matching media position for the current playback
646      *   speed.
647      * @param state The current playback state, one of the following values:
648      *       {@link #PLAYSTATE_STOPPED},
649      *       {@link #PLAYSTATE_PAUSED},
650      *       {@link #PLAYSTATE_PLAYING},
651      *       {@link #PLAYSTATE_FAST_FORWARDING},
652      *       {@link #PLAYSTATE_REWINDING},
653      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
654      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
655      *       {@link #PLAYSTATE_BUFFERING},
656      *       {@link #PLAYSTATE_ERROR}.
657      * @param timeInMs a 0 or positive value for the current media position expressed in ms
658      *    (same unit as for when sending the media duration, if applicable, with
659      *    {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the
660      *    {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not
661      *    known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state
662      *    is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
663      * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
664      *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
665      *    playing (e.g. when state is {@link #PLAYSTATE_ERROR}).
666      */
setPlaybackState(int state, long timeInMs, float playbackSpeed)667     public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
668         setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
669     }
670 
setPlaybackStateInt(int state, long timeInMs, float playbackSpeed, boolean hasPosition)671     private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed,
672             boolean hasPosition) {
673         synchronized(mCacheLock) {
674             if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
675                     || (mPlaybackSpeed != playbackSpeed)) {
676                 // store locally
677                 mPlaybackState = state;
678                 // distinguish between an application not knowing the current playback position
679                 // at the moment and an application using the API where only the playback state
680                 // is passed, not the playback position.
681                 if (hasPosition) {
682                     if (timeInMs < 0) {
683                         mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
684                     } else {
685                         mPlaybackPositionMs = timeInMs;
686                     }
687                 } else {
688                     mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN;
689                 }
690                 mPlaybackSpeed = playbackSpeed;
691                 // keep track of when the state change occurred
692                 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
693 
694                 // USE_SESSIONS
695                 if (mSession != null) {
696                     int pbState = PlaybackState.getStateFromRccState(state);
697                     long position = hasPosition ? mPlaybackPositionMs
698                             : PlaybackState.PLAYBACK_POSITION_UNKNOWN;
699 
700                     PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
701                     bob.setState(pbState, position, playbackSpeed, SystemClock.elapsedRealtime());
702                     bob.setErrorMessage(null);
703                     mSessionPlaybackState = bob.build();
704                     mSession.setPlaybackState(mSessionPlaybackState);
705                 }
706             }
707         }
708     }
709 
710     // TODO investigate if we still need position drift checking
onPositionDriftCheck()711     private void onPositionDriftCheck() {
712         if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
713         synchronized(mCacheLock) {
714             if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
715                 return;
716             }
717             if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
718                 if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
719                 return;
720             }
721             long estPos = mPlaybackPositionMs + (long)
722                     ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
723             long actPos = mPositionProvider.onGetPlaybackPosition();
724             if (actPos >= 0) {
725                 if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
726                     // drift happened, report the new position
727                     if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +"  est=" +estPos); }
728                     setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
729                 } else {
730                     if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +"  est=" + estPos); }
731                     // no drift, schedule the next drift check
732                     mEventHandler.sendMessageDelayed(
733                             mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
734                             getCheckPeriodFromSpeed(mPlaybackSpeed));
735                 }
736             } else {
737                 // invalid position (negative value), can't check for drift
738                 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
739             }
740         }
741     }
742 
743     /**
744      * Sets the flags for the media transport control buttons that this client supports.
745      * @param transportControlFlags A combination of the following flags:
746      *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
747      *      {@link #FLAG_KEY_MEDIA_REWIND},
748      *      {@link #FLAG_KEY_MEDIA_PLAY},
749      *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
750      *      {@link #FLAG_KEY_MEDIA_PAUSE},
751      *      {@link #FLAG_KEY_MEDIA_STOP},
752      *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
753      *      {@link #FLAG_KEY_MEDIA_NEXT},
754      *      {@link #FLAG_KEY_MEDIA_POSITION_UPDATE},
755      *      {@link #FLAG_KEY_MEDIA_RATING}.
756      */
setTransportControlFlags(int transportControlFlags)757     public void setTransportControlFlags(int transportControlFlags) {
758         synchronized(mCacheLock) {
759             // store locally
760             mTransportControlFlags = transportControlFlags;
761 
762             // USE_SESSIONS
763             if (mSession != null) {
764                 PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
765                 bob.setActions(
766                         PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
767                 mSessionPlaybackState = bob.build();
768                 mSession.setPlaybackState(mSessionPlaybackState);
769             }
770         }
771     }
772 
773     /**
774      * Interface definition for a callback to be invoked when one of the metadata values has
775      * been updated.
776      * Implement this interface to receive metadata updates after registering your listener
777      * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}.
778      */
779     public interface OnMetadataUpdateListener {
780         /**
781          * Called on the implementer to notify that the metadata field for the given key has
782          * been updated to the new value.
783          * @param key the identifier of the updated metadata field.
784          * @param newValue the Object storing the new value for the key.
785          */
onMetadataUpdate(int key, Object newValue)786         public abstract void onMetadataUpdate(int key, Object newValue);
787     }
788 
789     /**
790      * Sets the listener to be called whenever the metadata is updated.
791      * New metadata values will be received in the same thread as the one in which
792      * RemoteControlClient was created.
793      * @param l the metadata update listener
794      */
setMetadataUpdateListener(OnMetadataUpdateListener l)795     public void setMetadataUpdateListener(OnMetadataUpdateListener l) {
796         synchronized(mCacheLock) {
797             mMetadataUpdateListener = l;
798         }
799     }
800 
801 
802     /**
803      * Interface definition for a callback to be invoked when the media playback position is
804      * requested to be updated.
805      * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
806      */
807     public interface OnPlaybackPositionUpdateListener {
808         /**
809          * Called on the implementer to notify it that the playback head should be set at the given
810          * position. If the position can be changed from its current value, the implementor of
811          * the interface must also update the playback position using
812          * {@link #setPlaybackState(int, long, float)} to reflect the actual new
813          * position being used, regardless of whether it differs from the requested position.
814          * Failure to do so would cause the system to not know the new actual playback position,
815          * and user interface components would fail to show the user where playback resumed after
816          * the position was updated.
817          * @param newPositionMs the new requested position in the current media, expressed in ms.
818          */
onPlaybackPositionUpdate(long newPositionMs)819         void onPlaybackPositionUpdate(long newPositionMs);
820     }
821 
822     /**
823      * Interface definition for a callback to be invoked when the media playback position is
824      * queried.
825      * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
826      */
827     public interface OnGetPlaybackPositionListener {
828         /**
829          * Called on the implementer of the interface to query the current playback position.
830          * @return a negative value if the current playback position (or the last valid playback
831          *     position) is not known, or a zero or positive value expressed in ms indicating the
832          *     current position, or the last valid known position.
833          */
onGetPlaybackPosition()834         long onGetPlaybackPosition();
835     }
836 
837     /**
838      * Sets the listener to be called whenever the media playback position is requested
839      * to be updated.
840      * Notifications will be received in the same thread as the one in which RemoteControlClient
841      * was created.
842      * @param l the position update listener to be called
843      */
setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l)844     public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
845         synchronized(mCacheLock) {
846             mPositionUpdateListener = l;
847         }
848     }
849 
850     /**
851      * Sets the listener to be called whenever the media current playback position is needed.
852      * Queries will be received in the same thread as the one in which RemoteControlClient
853      * was created.
854      * @param l the listener to be called to retrieve the playback position
855      */
setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l)856     public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
857         synchronized(mCacheLock) {
858             mPositionProvider = l;
859             if ((mPositionProvider != null) && (mEventHandler != null)
860                     && playbackPositionShouldMove(mPlaybackState)) {
861                 // playback position is already moving, but now we have a position provider,
862                 // so schedule a drift check right now
863                 mEventHandler.sendMessageDelayed(
864                         mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
865                         0 /*check now*/);
866             }
867         }
868     }
869 
870     /**
871      * @hide
872      * Flag to reflect that the application controlling this RemoteControlClient sends playback
873      * position updates. The playback position being "readable" is considered from the application's
874      * point of view.
875      */
876     public static int MEDIA_POSITION_READABLE = 1 << 0;
877     /**
878      * @hide
879      * Flag to reflect that the application controlling this RemoteControlClient can receive
880      * playback position updates. The playback position being "writable"
881      * is considered from the application's point of view.
882      */
883     public static int MEDIA_POSITION_WRITABLE = 1 << 1;
884 
885     /** @hide */
886     public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
887     /** @hide */
888     // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
889     public final static int DEFAULT_PLAYBACK_VOLUME = 15;
890 
891     /**
892      * Lock for all cached data
893      */
894     private final Object mCacheLock = new Object();
895     /**
896      * Cache for the playback state.
897      * Access synchronized on mCacheLock
898      */
899     private int mPlaybackState = PLAYSTATE_NONE;
900     /**
901      * Time of last play state change
902      * Access synchronized on mCacheLock
903      */
904     private long mPlaybackStateChangeTimeMs = 0;
905     /**
906      * Last playback position in ms reported by the user
907      */
908     private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
909     /**
910      * Last playback speed reported by the user
911      */
912     private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
913     /**
914      * Cache for the artwork bitmap.
915      * Access synchronized on mCacheLock
916      * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
917      * accessed to be resized, in which case a copy will be made. This would add overhead in
918      * Bundle operations.
919      */
920     private Bitmap mOriginalArtwork;
921     /**
922      * Cache for the transport control mask.
923      * Access synchronized on mCacheLock
924      */
925     private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
926     /**
927      * Cache for the metadata strings.
928      * Access synchronized on mCacheLock
929      * This is re-initialized in apply() and so cannot be final.
930      */
931     private Bundle mMetadata = new Bundle();
932     /**
933      * Listener registered by user of RemoteControlClient to receive requests for playback position
934      * update requests.
935      */
936     private OnPlaybackPositionUpdateListener mPositionUpdateListener;
937     /**
938      * Provider registered by user of RemoteControlClient to provide the current playback position.
939      */
940     private OnGetPlaybackPositionListener mPositionProvider;
941     /**
942      * Listener registered by user of RemoteControlClient to receive edit changes to metadata
943      * it exposes.
944      */
945     private OnMetadataUpdateListener mMetadataUpdateListener;
946     /**
947      * The current remote control client generation ID across the system, as known by this object
948      */
949     private int mCurrentClientGenId = -1;
950 
951     /**
952      * The media button intent description associated with this remote control client
953      * (can / should include target component for intent handling, used when persisting media
954      *    button event receiver across reboots).
955      */
956     private final PendingIntent mRcMediaIntent;
957 
958     /**
959      * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true.
960      */
961     // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead
962     private boolean mNeedsPositionSync = false;
963 
964     /**
965      * Cache for the current playback state using Session APIs.
966      */
967     private PlaybackState mSessionPlaybackState = null;
968 
969     /**
970      * Cache for metadata using Session APIs. This is re-initialized in apply().
971      */
972     private MediaMetadata mMediaMetadata;
973 
974     /**
975      * @hide
976      * Accessor to media button intent description (includes target component)
977      */
getRcMediaIntent()978     public PendingIntent getRcMediaIntent() {
979         return mRcMediaIntent;
980     }
981 
982     /**
983      * @hide
984      * Default value for the unique identifier
985      */
986     public final static int RCSE_ID_UNREGISTERED = -1;
987 
988     // USE_SESSIONS
989     private MediaSession.Callback mTransportListener = new MediaSession.Callback() {
990 
991         @Override
992         public void onSeekTo(long pos) {
993             RemoteControlClient.this.onSeekTo(mCurrentClientGenId, pos);
994         }
995 
996         @Override
997         public void onSetRating(Rating rating) {
998             if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) {
999                 onUpdateMetadata(mCurrentClientGenId, MetadataEditor.RATING_KEY_BY_USER, rating);
1000             }
1001         }
1002     };
1003 
1004     private EventHandler mEventHandler;
1005     private final static int MSG_POSITION_DRIFT_CHECK = 11;
1006 
1007     private class EventHandler extends Handler {
EventHandler(RemoteControlClient rcc, Looper looper)1008         public EventHandler(RemoteControlClient rcc, Looper looper) {
1009             super(looper);
1010         }
1011 
1012         @Override
handleMessage(Message msg)1013         public void handleMessage(Message msg) {
1014             switch(msg.what) {
1015                 case MSG_POSITION_DRIFT_CHECK:
1016                     onPositionDriftCheck();
1017                     break;
1018                 default:
1019                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
1020             }
1021         }
1022     }
1023 
1024     //===========================================================
1025     // Message handlers
1026 
onSeekTo(int generationId, long timeMs)1027     private void onSeekTo(int generationId, long timeMs) {
1028         synchronized (mCacheLock) {
1029             if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
1030                 mPositionUpdateListener.onPlaybackPositionUpdate(timeMs);
1031             }
1032         }
1033     }
1034 
onUpdateMetadata(int generationId, int key, Object value)1035     private void onUpdateMetadata(int generationId, int key, Object value) {
1036         synchronized (mCacheLock) {
1037             if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
1038                 mMetadataUpdateListener.onMetadataUpdate(key, value);
1039             }
1040         }
1041     }
1042 
1043     //===========================================================
1044     // Internal utilities
1045 
1046     /**
1047      * Returns whether, for the given playback state, the playback position is expected to
1048      * be changing.
1049      * @param playstate the playback state to evaluate
1050      * @return true during any form of playback, false if it's not playing anything while in this
1051      *     playback state
1052      */
playbackPositionShouldMove(int playstate)1053     static boolean playbackPositionShouldMove(int playstate) {
1054         switch(playstate) {
1055             case PLAYSTATE_STOPPED:
1056             case PLAYSTATE_PAUSED:
1057             case PLAYSTATE_BUFFERING:
1058             case PLAYSTATE_ERROR:
1059             case PLAYSTATE_SKIPPING_FORWARDS:
1060             case PLAYSTATE_SKIPPING_BACKWARDS:
1061                 return false;
1062             case PLAYSTATE_PLAYING:
1063             case PLAYSTATE_FAST_FORWARDING:
1064             case PLAYSTATE_REWINDING:
1065             default:
1066                 return true;
1067         }
1068     }
1069 
1070     /**
1071      * Period for playback position drift checks, 15s when playing at 1x or slower.
1072      */
1073     private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
1074     /**
1075      * Minimum period for playback position drift checks, never more often when every 2s, when
1076      * fast forwarding or rewinding.
1077      */
1078     private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
1079     /**
1080      * The value above which the difference between client-reported playback position and
1081      * estimated position is considered a drift.
1082      */
1083     private final static long POSITION_DRIFT_MAX_MS = 500;
1084     /**
1085      * Compute the period at which the estimated playback position should be compared against the
1086      * actual playback position. Is a funciton of playback speed.
1087      * @param speed 1.0f is normal playback speed
1088      * @return the period in ms
1089      */
getCheckPeriodFromSpeed(float speed)1090     private static long getCheckPeriodFromSpeed(float speed) {
1091         if (Math.abs(speed) <= 1.0f) {
1092             return POSITION_REFRESH_PERIOD_PLAYING_MS;
1093         } else {
1094             return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)),
1095                     POSITION_REFRESH_PERIOD_MIN_MS);
1096         }
1097     }
1098 }
1099