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