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