• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.session;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.app.Activity;
24 import android.app.PendingIntent;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.media.AudioAttributes;
30 import android.media.MediaDescription;
31 import android.media.MediaMetadata;
32 import android.media.Rating;
33 import android.media.VolumeProvider;
34 import android.media.session.MediaSessionManager.RemoteUserInfo;
35 import android.net.Uri;
36 import android.os.BadParcelableException;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Looper;
42 import android.os.Message;
43 import android.os.Parcel;
44 import android.os.Parcelable;
45 import android.os.Process;
46 import android.os.RemoteException;
47 import android.os.ResultReceiver;
48 import android.service.media.MediaBrowserService;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.util.Pair;
52 import android.view.KeyEvent;
53 import android.view.ViewConfiguration;
54 
55 import java.lang.annotation.Retention;
56 import java.lang.annotation.RetentionPolicy;
57 import java.lang.ref.WeakReference;
58 import java.util.List;
59 import java.util.Objects;
60 
61 /**
62  * Allows interaction with media controllers, volume keys, media buttons, and
63  * transport controls.
64  * <p>
65  * A MediaSession should be created when an app wants to publish media playback
66  * information or handle media keys. In general an app only needs one session
67  * for all playback, though multiple sessions can be created to provide finer
68  * grain controls of media.
69  * <p>
70  * Once a session is created the owner of the session may pass its
71  * {@link #getSessionToken() session token} to other processes to allow them to
72  * create a {@link MediaController} to interact with the session.
73  * <p>
74  * To receive commands, media keys, and other events a {@link Callback} must be
75  * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
76  * setActive(true)} must be called.
77  * <p>
78  * When an app is finished performing playback it must call {@link #release()}
79  * to clean up the session and notify any controllers.
80  * <p>
81  * MediaSession objects are thread safe.
82  */
83 public final class MediaSession {
84     static final String TAG = "MediaSession";
85 
86     /**
87      * Set this flag on the session to indicate that it can handle media button
88      * events.
89      * @deprecated This flag is no longer used. All media sessions are expected to handle media
90      * button events now.
91      */
92     @Deprecated
93     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
94 
95     /**
96      * Set this flag on the session to indicate that it handles transport
97      * control commands through its {@link Callback}.
98      * @deprecated This flag is no longer used. All media sessions are expected to handle transport
99      * controls now.
100      */
101     @Deprecated
102     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
103 
104     /**
105      * System only flag for a session that needs to have priority over all other
106      * sessions. This flag ensures this session will receive media button events
107      * regardless of the current ordering in the system.
108      * If there are two or more sessions with this flag, the last session that sets this flag
109      * will be the global priority session.
110      *
111      * @hide
112      */
113     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
114     public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
115 
116     /**
117      * @hide
118      */
119     public static final int INVALID_UID = -1;
120 
121     /**
122      * @hide
123      */
124     public static final int INVALID_PID = -1;
125 
126     /** @hide */
127     @Retention(RetentionPolicy.SOURCE)
128     @IntDef(flag = true, value = {
129             FLAG_HANDLES_MEDIA_BUTTONS,
130             FLAG_HANDLES_TRANSPORT_CONTROLS,
131             FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
132     public @interface SessionFlags { }
133 
134     private final Object mLock = new Object();
135     private Context mContext;
136     private final int mMaxBitmapSize;
137 
138     private final Token mSessionToken;
139     private final MediaController mController;
140     private final ISession mBinder;
141     private final CallbackStub mCbStub;
142 
143     // Do not change the name of mCallback. Support lib accesses this by using reflection.
144     @UnsupportedAppUsage
145     private CallbackMessageHandler mCallback;
146     private VolumeProvider mVolumeProvider;
147     private PlaybackState mPlaybackState;
148 
149     private boolean mActive = false;
150 
151     /**
152      * Creates a new session. The session will automatically be registered with
153      * the system but will not be published until {@link #setActive(boolean)
154      * setActive(true)} is called. You must call {@link #release()} when
155      * finished with the session.
156      * <p>
157      * Note that {@link RuntimeException} will be thrown if an app creates too many sessions.
158      *
159      * @param context The context to use to create the session.
160      * @param tag A short name for debugging purposes.
161      */
MediaSession(@onNull Context context, @NonNull String tag)162     public MediaSession(@NonNull Context context, @NonNull String tag) {
163         this(context, tag, null);
164     }
165 
166     /**
167      * Creates a new session. The session will automatically be registered with
168      * the system but will not be published until {@link #setActive(boolean)
169      * setActive(true)} is called. You must call {@link #release()} when
170      * finished with the session.
171      * <p>
172      * The {@code sessionInfo} can include additional unchanging information about this session.
173      * For example, it can include the version of the application, or the list of the custom
174      * commands that this session supports.
175      * <p>
176      * Note that {@link RuntimeException} will be thrown if an app creates too many sessions.
177      *
178      * @param context The context to use to create the session.
179      * @param tag A short name for debugging purposes.
180      * @param sessionInfo A bundle for additional information about this session.
181      *                    Controllers can get this information by calling
182      *                    {@link MediaController#getSessionInfo()}.
183      *                    An {@link IllegalArgumentException} will be thrown if this contains
184      *                    any non-framework Parcelable objects.
185      */
MediaSession(@onNull Context context, @NonNull String tag, @Nullable Bundle sessionInfo)186     public MediaSession(@NonNull Context context, @NonNull String tag,
187             @Nullable Bundle sessionInfo) {
188         if (context == null) {
189             throw new IllegalArgumentException("context cannot be null.");
190         }
191         if (TextUtils.isEmpty(tag)) {
192             throw new IllegalArgumentException("tag cannot be null or empty");
193         }
194         if (hasCustomParcelable(sessionInfo)) {
195             throw new IllegalArgumentException("sessionInfo shouldn't contain any custom "
196                     + "parcelables");
197         }
198 
199         mContext = context;
200         mMaxBitmapSize = context.getResources().getDimensionPixelSize(
201                 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
202         mCbStub = new CallbackStub(this);
203         MediaSessionManager manager = (MediaSessionManager) context
204                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
205         try {
206             mBinder = manager.createSession(mCbStub, tag, sessionInfo);
207             mSessionToken = new Token(Process.myUid(), mBinder.getController());
208             mController = new MediaController(context, mSessionToken);
209         } catch (RemoteException e) {
210             throw new RuntimeException("Remote error creating session.", e);
211         }
212     }
213 
214     /**
215      * Set the callback to receive updates for the MediaSession. This includes
216      * media button events and transport controls. The caller's thread will be
217      * used to post updates.
218      * <p>
219      * Set the callback to null to stop receiving updates.
220      *
221      * @param callback The callback object
222      */
setCallback(@ullable Callback callback)223     public void setCallback(@Nullable Callback callback) {
224         setCallback(callback, null);
225     }
226 
227     /**
228      * Set the callback to receive updates for the MediaSession. This includes
229      * media button events and transport controls.
230      * <p>
231      * Set the callback to null to stop receiving updates.
232      *
233      * @param callback The callback to receive updates on.
234      * @param handler The handler that events should be posted on.
235      */
setCallback(@ullable Callback callback, @Nullable Handler handler)236     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
237         synchronized (mLock) {
238             if (mCallback != null) {
239                 // We're updating the callback, clear the session from the old one.
240                 mCallback.mCallback.mSession = null;
241                 mCallback.removeCallbacksAndMessages(null);
242             }
243             if (callback == null) {
244                 mCallback = null;
245                 return;
246             }
247             Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
248             callback.mSession = this;
249             CallbackMessageHandler msgHandler = new CallbackMessageHandler(looper, callback);
250             mCallback = msgHandler;
251         }
252     }
253 
254     /**
255      * Set an intent for launching UI for this Session. This can be used as a
256      * quick link to an ongoing media screen. The intent should be for an
257      * activity that may be started using {@link Activity#startActivity(Intent)}.
258      *
259      * @param pi The intent to launch to show UI for this Session.
260      */
setSessionActivity(@ullable PendingIntent pi)261     public void setSessionActivity(@Nullable PendingIntent pi) {
262         try {
263             mBinder.setLaunchPendingIntent(pi);
264         } catch (RemoteException e) {
265             Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
266         }
267     }
268 
269     /**
270      * Set a pending intent for your media button receiver to allow restarting playback after the
271      * session has been stopped.
272      *
273      * <p>If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be
274      * sent via the pending intent.
275      *
276      * <p>The provided {@link PendingIntent} must not target an activity. On apps targeting Android
277      * V and above, passing an activity pending intent to this method causes an {@link
278      * IllegalArgumentException}. On apps targeting Android U and below, passing an activity pending
279      * intent causes the call to be ignored. Refer to this <a
280      * href="https://developer.android.com/guide/components/activities/background-starts">guide</a>
281      * for more information.
282      *
283      * <p>The pending intent is recommended to be explicit to follow the security recommendation of
284      * {@link PendingIntent#getService}.
285      *
286      * @param mbr The {@link PendingIntent} to send the media button event to.
287      * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead.
288      * @throws IllegalArgumentException if the pending intent targets an activity on apps targeting
289      * Android V and above.
290      */
291     @Deprecated
setMediaButtonReceiver(@ullable PendingIntent mbr)292     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
293         try {
294             mBinder.setMediaButtonReceiver(mbr);
295         } catch (RemoteException e) {
296             e.rethrowFromSystemServer();
297         }
298     }
299 
300     /**
301      * Set the component name of the manifest-declared {@link android.content.BroadcastReceiver}
302      * class that should receive media buttons. This allows restarting playback after the session
303      * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON}
304      * intent will be sent to the broadcast receiver. On apps targeting Android U and above, this
305      * will throw an {@link IllegalArgumentException} if the provided {@link ComponentName} does not
306      * resolve to an existing {@link android.content.BroadcastReceiver broadcast receiver}.
307      *
308      * <p>Note: The given {@link android.content.BroadcastReceiver} should belong to the same
309      * package as the context that was given when creating {@link MediaSession}.
310      *
311      * @param broadcastReceiver the component name of the BroadcastReceiver class
312      * @throws IllegalArgumentException if {@code broadcastReceiver} does not exist on apps
313      *     targeting Android U and above
314      */
setMediaButtonBroadcastReceiver(@ullable ComponentName broadcastReceiver)315     public void setMediaButtonBroadcastReceiver(@Nullable ComponentName broadcastReceiver) {
316         try {
317             if (broadcastReceiver != null) {
318                 if (!TextUtils.equals(broadcastReceiver.getPackageName(),
319                         mContext.getPackageName())) {
320                     throw new IllegalArgumentException("broadcastReceiver should belong to the same"
321                             + " package as the context given when creating MediaSession.");
322                 }
323             }
324             mBinder.setMediaButtonBroadcastReceiver(broadcastReceiver);
325         } catch (RemoteException e) {
326             e.rethrowFromSystemServer();
327         }
328     }
329 
330     /**
331      * Set any flags for the session.
332      *
333      * @param flags The flags to set for this session.
334      */
setFlags(@essionFlags int flags)335     public void setFlags(@SessionFlags int flags) {
336         try {
337             mBinder.setFlags(flags);
338         } catch (RemoteException e) {
339             Log.wtf(TAG, "Failure in setFlags.", e);
340         }
341     }
342 
343     /**
344      * Set the attributes for this session's audio. This will affect the
345      * system's volume handling for this session. If
346      * {@link #setPlaybackToRemote} was previously called it will stop receiving
347      * volume commands and the system will begin sending volume changes to the
348      * appropriate stream.
349      * <p>
350      * By default sessions use attributes for media.
351      *
352      * @param attributes The {@link AudioAttributes} for this session's audio.
353      */
setPlaybackToLocal(AudioAttributes attributes)354     public void setPlaybackToLocal(AudioAttributes attributes) {
355         if (attributes == null) {
356             throw new IllegalArgumentException("Attributes cannot be null for local playback.");
357         }
358         try {
359             mBinder.setPlaybackToLocal(attributes);
360         } catch (RemoteException e) {
361             Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
362         }
363     }
364 
365     /**
366      * Configure this session to use remote volume handling. This must be called
367      * to receive volume button events, otherwise the system will adjust the
368      * appropriate stream volume for this session. If
369      * {@link #setPlaybackToLocal} was previously called the system will stop
370      * handling volume changes for this session and pass them to the volume
371      * provider instead.
372      *
373      * @param volumeProvider The provider that will handle volume changes. May
374      *            not be null.
375      */
setPlaybackToRemote(@onNull VolumeProvider volumeProvider)376     public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
377         if (volumeProvider == null) {
378             throw new IllegalArgumentException("volumeProvider may not be null!");
379         }
380         synchronized (mLock) {
381             mVolumeProvider = volumeProvider;
382         }
383         volumeProvider.setCallback(new VolumeProvider.Callback() {
384             @Override
385             public void onVolumeChanged(VolumeProvider volumeProvider) {
386                 notifyRemoteVolumeChanged(volumeProvider);
387             }
388         });
389 
390         try {
391             mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
392                     volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId());
393             mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
394         } catch (RemoteException e) {
395             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
396         }
397     }
398 
399     /**
400      * Set if this session is currently active and ready to receive commands. If
401      * set to false your session's controller may not be discoverable. You must
402      * set the session to active before it can start receiving media button
403      * events or transport commands.
404      *
405      * @param active Whether this session is active or not.
406      */
setActive(boolean active)407     public void setActive(boolean active) {
408         if (mActive == active) {
409             return;
410         }
411         try {
412             mBinder.setActive(active);
413             mActive = active;
414         } catch (RemoteException e) {
415             Log.wtf(TAG, "Failure in setActive.", e);
416         }
417     }
418 
419     /**
420      * Get the current active state of this session.
421      *
422      * @return True if the session is active, false otherwise.
423      */
isActive()424     public boolean isActive() {
425         return mActive;
426     }
427 
428     /**
429      * Send a proprietary event to all MediaControllers listening to this
430      * Session. It's up to the Controller/Session owner to determine the meaning
431      * of any events.
432      *
433      * @param event The name of the event to send
434      * @param extras Any extras included with the event
435      */
sendSessionEvent(@onNull String event, @Nullable Bundle extras)436     public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
437         if (TextUtils.isEmpty(event)) {
438             throw new IllegalArgumentException("event cannot be null or empty");
439         }
440         try {
441             mBinder.sendEvent(event, extras);
442         } catch (RemoteException e) {
443             Log.wtf(TAG, "Error sending event", e);
444         }
445     }
446 
447     /**
448      * This must be called when an app has finished performing playback. If
449      * playback is expected to start again shortly the session can be left open,
450      * but it must be released if your activity or service is being destroyed.
451      */
release()452     public void release() {
453         setCallback(null);
454         try {
455             mBinder.destroySession();
456         } catch (RemoteException e) {
457             Log.wtf(TAG, "Error releasing session: ", e);
458         }
459     }
460 
461     /**
462      * Retrieve a token object that can be used by apps to create a
463      * {@link MediaController} for interacting with this session. The owner of
464      * the session is responsible for deciding how to distribute these tokens.
465      *
466      * @return A token that can be used to create a MediaController for this
467      *         session
468      */
getSessionToken()469     public @NonNull Token getSessionToken() {
470         return mSessionToken;
471     }
472 
473     /**
474      * Get a controller for this session. This is a convenience method to avoid
475      * having to cache your own controller in process.
476      *
477      * @return A controller for this session.
478      */
getController()479     public @NonNull MediaController getController() {
480         return mController;
481     }
482 
483     /**
484      * Update the current playback state.
485      *
486      * @param state The current state of playback
487      */
setPlaybackState(@ullable PlaybackState state)488     public void setPlaybackState(@Nullable PlaybackState state) {
489         mPlaybackState = state;
490         try {
491             mBinder.setPlaybackState(state);
492         } catch (RemoteException e) {
493             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
494         }
495     }
496 
497     /**
498      * Update the current metadata. New metadata can be created using
499      * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
500      * the size of the bitmap to replace large bitmaps with a scaled down copy.
501      *
502      * @param metadata The new metadata
503      * @see android.media.MediaMetadata.Builder#putBitmap
504      */
setMetadata(@ullable MediaMetadata metadata)505     public void setMetadata(@Nullable MediaMetadata metadata) {
506         long duration = -1;
507         int fields = 0;
508         MediaDescription description = null;
509         if (metadata != null) {
510             metadata = new MediaMetadata.Builder(metadata)
511                     .setBitmapDimensionLimit(mMaxBitmapSize)
512                     .build();
513             if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
514                 duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
515             }
516             fields = metadata.size();
517             description = metadata.getDescription();
518         }
519         String metadataDescription = "size=" + fields + ", description=" + description;
520 
521         try {
522             mBinder.setMetadata(metadata, duration, metadataDescription);
523         } catch (RemoteException e) {
524             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
525         }
526     }
527 
528     /**
529      * Update the list of items in the play queue. It is an ordered list and
530      * should contain the current item, and previous or upcoming items if they
531      * exist. Specify null if there is no current play queue.
532      * <p>
533      * The queue should be of reasonable size. If the play queue is unbounded
534      * within your app, it is better to send a reasonable amount in a sliding
535      * window instead.
536      *
537      * @param queue A list of items in the play queue.
538      */
setQueue(@ullable List<QueueItem> queue)539     public void setQueue(@Nullable List<QueueItem> queue) {
540         try {
541             if (queue == null) {
542                 mBinder.resetQueue();
543             } else {
544                 IBinder binder = mBinder.getBinderForSetQueue();
545                 ParcelableListBinder.send(binder, queue);
546             }
547         } catch (RemoteException e) {
548             Log.wtf("Dead object in setQueue.", e);
549         }
550     }
551 
552     /**
553      * Set the title of the play queue. The UI should display this title along
554      * with the play queue itself.
555      * e.g. "Play Queue", "Now Playing", or an album name.
556      *
557      * @param title The title of the play queue.
558      */
setQueueTitle(@ullable CharSequence title)559     public void setQueueTitle(@Nullable CharSequence title) {
560         try {
561             mBinder.setQueueTitle(title);
562         } catch (RemoteException e) {
563             Log.wtf("Dead object in setQueueTitle.", e);
564         }
565     }
566 
567     /**
568      * Set the style of rating used by this session. Apps trying to set the
569      * rating should use this style. Must be one of the following:
570      * <ul>
571      * <li>{@link Rating#RATING_NONE}</li>
572      * <li>{@link Rating#RATING_3_STARS}</li>
573      * <li>{@link Rating#RATING_4_STARS}</li>
574      * <li>{@link Rating#RATING_5_STARS}</li>
575      * <li>{@link Rating#RATING_HEART}</li>
576      * <li>{@link Rating#RATING_PERCENTAGE}</li>
577      * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
578      * </ul>
579      */
setRatingType(@ating.Style int type)580     public void setRatingType(@Rating.Style int type) {
581         try {
582             mBinder.setRatingType(type);
583         } catch (RemoteException e) {
584             Log.e(TAG, "Error in setRatingType.", e);
585         }
586     }
587 
588     /**
589      * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
590      * be made as to how a {@link MediaController} will handle these extras.
591      * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
592      *
593      * @param extras The extras associated with the {@link MediaSession}.
594      */
setExtras(@ullable Bundle extras)595     public void setExtras(@Nullable Bundle extras) {
596         try {
597             mBinder.setExtras(extras);
598         } catch (RemoteException e) {
599             Log.wtf("Dead object in setExtras.", e);
600         }
601     }
602 
603     /**
604      * Gets the controller information who sent the current request.
605      * <p>
606      * Note: This is only valid while in a request callback, such as {@link Callback#onPlay}.
607      *
608      * @throws IllegalStateException If this method is called outside of {@link Callback} methods.
609      * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
610      */
getCurrentControllerInfo()611     public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
612         if (mCallback == null || mCallback.mCurrentControllerInfo == null) {
613             throw new IllegalStateException(
614                     "This should be called inside of MediaSession.Callback methods");
615         }
616         return mCallback.mCurrentControllerInfo;
617     }
618 
619     /**
620      * Notify the system that the remote volume changed.
621      *
622      * @param provider The provider that is handling volume changes.
623      * @hide
624      */
notifyRemoteVolumeChanged(VolumeProvider provider)625     public void notifyRemoteVolumeChanged(VolumeProvider provider) {
626         synchronized (mLock) {
627             if (provider == null || provider != mVolumeProvider) {
628                 Log.w(TAG, "Received update from stale volume provider");
629                 return;
630             }
631         }
632         try {
633             mBinder.setCurrentVolume(provider.getCurrentVolume());
634         } catch (RemoteException e) {
635             Log.e(TAG, "Error in notifyVolumeChanged", e);
636         }
637     }
638 
639     /**
640      * Returns the name of the package that sent the last media button, transport control, or
641      * command from controllers and the system. This is only valid while in a request callback, such
642      * as {@link Callback#onPlay}.
643      *
644      * @hide
645      */
646     @UnsupportedAppUsage
getCallingPackage()647     public String getCallingPackage() {
648         if (mCallback != null && mCallback.mCurrentControllerInfo != null) {
649             return mCallback.mCurrentControllerInfo.getPackageName();
650         }
651         return null;
652     }
653 
654     /**
655      * Returns whether the given bundle includes non-framework Parcelables.
656      */
hasCustomParcelable(@ullable Bundle bundle)657     static boolean hasCustomParcelable(@Nullable Bundle bundle) {
658         if (bundle == null) {
659             return false;
660         }
661 
662         // Try writing the bundle to parcel, and read it with framework classloader.
663         Parcel parcel = null;
664         try {
665             parcel = Parcel.obtain();
666             parcel.writeBundle(bundle);
667             parcel.setDataPosition(0);
668             Bundle out = parcel.readBundle(null);
669 
670             for (String key : out.keySet()) {
671                 out.get(key);
672             }
673         } catch (BadParcelableException e) {
674             Log.d(TAG, "Custom parcelable in bundle.", e);
675             return true;
676         } finally {
677             if (parcel != null) {
678                 parcel.recycle();
679             }
680         }
681         return false;
682     }
683 
dispatchPrepare(RemoteUserInfo caller)684     void dispatchPrepare(RemoteUserInfo caller) {
685         postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
686     }
687 
dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras)688     void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
689         postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
690     }
691 
dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras)692     void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
693         postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
694     }
695 
dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras)696     void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
697         postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
698     }
699 
dispatchPlay(RemoteUserInfo caller)700     void dispatchPlay(RemoteUserInfo caller) {
701         postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
702     }
703 
dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras)704     void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
705         postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
706     }
707 
dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras)708     void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
709         postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
710     }
711 
dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras)712     void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
713         postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
714     }
715 
dispatchSkipToItem(RemoteUserInfo caller, long id)716     void dispatchSkipToItem(RemoteUserInfo caller, long id) {
717         postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
718     }
719 
dispatchPause(RemoteUserInfo caller)720     void dispatchPause(RemoteUserInfo caller) {
721         postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
722     }
723 
dispatchStop(RemoteUserInfo caller)724     void dispatchStop(RemoteUserInfo caller) {
725         postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
726     }
727 
dispatchNext(RemoteUserInfo caller)728     void dispatchNext(RemoteUserInfo caller) {
729         postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
730     }
731 
dispatchPrevious(RemoteUserInfo caller)732     void dispatchPrevious(RemoteUserInfo caller) {
733         postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
734     }
735 
dispatchFastForward(RemoteUserInfo caller)736     void dispatchFastForward(RemoteUserInfo caller) {
737         postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
738     }
739 
dispatchRewind(RemoteUserInfo caller)740     void dispatchRewind(RemoteUserInfo caller) {
741         postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
742     }
743 
dispatchSeekTo(RemoteUserInfo caller, long pos)744     void dispatchSeekTo(RemoteUserInfo caller, long pos) {
745         postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
746     }
747 
dispatchRate(RemoteUserInfo caller, Rating rating)748     void dispatchRate(RemoteUserInfo caller, Rating rating) {
749         postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
750     }
751 
dispatchSetPlaybackSpeed(RemoteUserInfo caller, float speed)752     void dispatchSetPlaybackSpeed(RemoteUserInfo caller, float speed) {
753         postToCallback(caller, CallbackMessageHandler.MSG_SET_PLAYBACK_SPEED, speed, null);
754     }
755 
dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args)756     void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
757         postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
758     }
759 
dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent)760     void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
761         postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
762     }
763 
dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent, long delay)764     void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
765             long delay) {
766         postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
767                 mediaButtonIntent, null, delay);
768     }
769 
dispatchAdjustVolume(RemoteUserInfo caller, int direction)770     void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
771         postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
772     }
773 
dispatchSetVolumeTo(RemoteUserInfo caller, int volume)774     void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
775         postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
776     }
777 
dispatchCommand(RemoteUserInfo caller, String command, Bundle args, ResultReceiver resultCb)778     void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
779             ResultReceiver resultCb) {
780         Command cmd = new Command(command, args, resultCb);
781         postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
782     }
783 
postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data)784     void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
785         postToCallbackDelayed(caller, what, obj, data, 0);
786     }
787 
postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data, long delay)788     void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
789             long delay) {
790         synchronized (mLock) {
791             if (mCallback != null) {
792                 mCallback.post(caller, what, obj, data, delay);
793             }
794         }
795     }
796 
797     /**
798      * Represents an ongoing session. This may be passed to apps by the session
799      * owner to allow them to create a {@link MediaController} to communicate with
800      * the session.
801      */
802     public static final class Token implements Parcelable {
803 
804         private final int mUid;
805         private final ISessionController mBinder;
806 
807         /**
808          * @hide
809          */
Token(int uid, ISessionController binder)810         public Token(int uid, ISessionController binder) {
811             mUid = uid;
812             mBinder = binder;
813         }
814 
Token(Parcel in)815         Token(Parcel in) {
816             mUid = in.readInt();
817             mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
818         }
819 
820         @Override
describeContents()821         public int describeContents() {
822             return 0;
823         }
824 
825         @Override
writeToParcel(Parcel dest, int flags)826         public void writeToParcel(Parcel dest, int flags) {
827             dest.writeInt(mUid);
828             dest.writeStrongBinder(mBinder.asBinder());
829         }
830 
831         @Override
hashCode()832         public int hashCode() {
833             final int prime = 31;
834             int result = mUid;
835             result = prime * result + (mBinder == null ? 0 : mBinder.asBinder().hashCode());
836             return result;
837         }
838 
839         @Override
equals(Object obj)840         public boolean equals(Object obj) {
841             if (this == obj)
842                 return true;
843             if (obj == null)
844                 return false;
845             if (getClass() != obj.getClass())
846                 return false;
847             Token other = (Token) obj;
848             if (mUid != other.mUid) {
849                 return false;
850             }
851             if (mBinder == null || other.mBinder == null) {
852                 return mBinder == other.mBinder;
853             }
854             return Objects.equals(mBinder.asBinder(), other.mBinder.asBinder());
855         }
856 
857         /**
858          * Gets the UID of the application that created the media session.
859          * @hide
860          */
861         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
getUid()862         public int getUid() {
863             return mUid;
864         }
865 
866         /**
867          * Gets the controller binder in this token.
868          * @hide
869          */
getBinder()870         public ISessionController getBinder() {
871             return mBinder;
872         }
873 
874         public static final @android.annotation.NonNull Parcelable.Creator<Token> CREATOR =
875                 new Parcelable.Creator<Token>() {
876             @Override
877             public Token createFromParcel(Parcel in) {
878                 return new Token(in);
879             }
880 
881             @Override
882             public Token[] newArray(int size) {
883                 return new Token[size];
884             }
885         };
886     }
887 
888     /**
889      * Receives media buttons, transport controls, and commands from controllers
890      * and the system. A callback may be set using {@link #setCallback}.
891      */
892     public abstract static class Callback {
893 
894         private MediaSession mSession;
895         private CallbackMessageHandler mHandler;
896         private boolean mMediaPlayPauseKeyPending;
897 
Callback()898         public Callback() {
899         }
900 
901         /**
902          * Called when a controller has sent a command to this session.
903          * The owner of the session may handle custom commands but is not
904          * required to.
905          *
906          * @param command The command name.
907          * @param args Optional parameters for the command, may be null.
908          * @param cb A result receiver to which a result may be sent by the command, may be null.
909          */
onCommand(@onNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb)910         public void onCommand(@NonNull String command, @Nullable Bundle args,
911                 @Nullable ResultReceiver cb) {
912         }
913 
914         /**
915          * Called when a media button is pressed and this session has the
916          * highest priority or a controller sends a media button event to the
917          * session. The default behavior will call the relevant method if the
918          * action for it was set.
919          * <p>
920          * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
921          * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
922          *
923          * @param mediaButtonIntent an intent containing the KeyEvent as an
924          *            extra
925          * @return True if the event was handled, false otherwise.
926          */
onMediaButtonEvent(@onNull Intent mediaButtonIntent)927         public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
928             if (mSession != null && mHandler != null
929                     && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
930                 KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.class);
931                 if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
932                     PlaybackState state = mSession.mPlaybackState;
933                     long validActions = state == null ? 0 : state.getActions();
934                     switch (ke.getKeyCode()) {
935                         case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
936                         case KeyEvent.KEYCODE_HEADSETHOOK:
937                             if (ke.getRepeatCount() > 0) {
938                                 // Consider long-press as a single tap.
939                                 handleMediaPlayPauseKeySingleTapIfPending();
940                             } else if (mMediaPlayPauseKeyPending) {
941                                 // Consider double tap as the next.
942                                 mHandler.removeMessages(CallbackMessageHandler
943                                         .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
944                                 mMediaPlayPauseKeyPending = false;
945                                 if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
946                                     onSkipToNext();
947                                 }
948                             } else {
949                                 mMediaPlayPauseKeyPending = true;
950                                 mSession.dispatchMediaButtonDelayed(
951                                         mSession.getCurrentControllerInfo(),
952                                         mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
953                             }
954                             return true;
955                         default:
956                             // If another key is pressed within double tap timeout, consider the
957                             // pending play/pause as a single tap to handle media keys in order.
958                             handleMediaPlayPauseKeySingleTapIfPending();
959                             break;
960                     }
961 
962                     switch (ke.getKeyCode()) {
963                         case KeyEvent.KEYCODE_MEDIA_PLAY:
964                             if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
965                                 onPlay();
966                                 return true;
967                             }
968                             break;
969                         case KeyEvent.KEYCODE_MEDIA_PAUSE:
970                             if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
971                                 onPause();
972                                 return true;
973                             }
974                             break;
975                         case KeyEvent.KEYCODE_MEDIA_NEXT:
976                             if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
977                                 onSkipToNext();
978                                 return true;
979                             }
980                             break;
981                         case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
982                             if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
983                                 onSkipToPrevious();
984                                 return true;
985                             }
986                             break;
987                         case KeyEvent.KEYCODE_MEDIA_STOP:
988                             if ((validActions & PlaybackState.ACTION_STOP) != 0) {
989                                 onStop();
990                                 return true;
991                             }
992                             break;
993                         case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
994                             if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
995                                 onFastForward();
996                                 return true;
997                             }
998                             break;
999                         case KeyEvent.KEYCODE_MEDIA_REWIND:
1000                             if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
1001                                 onRewind();
1002                                 return true;
1003                             }
1004                             break;
1005                     }
1006                 }
1007             }
1008             return false;
1009         }
1010 
handleMediaPlayPauseKeySingleTapIfPending()1011         private void handleMediaPlayPauseKeySingleTapIfPending() {
1012             if (!mMediaPlayPauseKeyPending) {
1013                 return;
1014             }
1015             mMediaPlayPauseKeyPending = false;
1016             mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
1017             PlaybackState state = mSession.mPlaybackState;
1018             long validActions = state == null ? 0 : state.getActions();
1019             boolean isPlaying = state != null
1020                     && state.getState() == PlaybackState.STATE_PLAYING;
1021             boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
1022                     | PlaybackState.ACTION_PLAY)) != 0;
1023             boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
1024                     | PlaybackState.ACTION_PAUSE)) != 0;
1025             if (isPlaying && canPause) {
1026                 onPause();
1027             } else if (!isPlaying && canPlay) {
1028                 onPlay();
1029             }
1030         }
1031 
1032         /**
1033          * Override to handle requests to prepare playback. During the preparation, a session should
1034          * not hold audio focus in order to allow other sessions play seamlessly. The state of
1035          * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
1036          * done.
1037          */
onPrepare()1038         public void onPrepare() {
1039         }
1040 
1041         /**
1042          * Override to handle requests to prepare for playing a specific mediaId that was provided
1043          * by your app's {@link MediaBrowserService}. During the preparation, a session should not
1044          * hold audio focus in order to allow other sessions play seamlessly. The state of playback
1045          * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
1046          * The playback of the prepared content should start in the implementation of
1047          * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
1048          * playback without preparation.
1049          */
onPrepareFromMediaId(String mediaId, Bundle extras)1050         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
1051         }
1052 
1053         /**
1054          * Override to handle requests to prepare playback from a search query. An empty query
1055          * indicates that the app may prepare any music. The implementation should attempt to make a
1056          * smart choice about what to play. During the preparation, a session should not hold audio
1057          * focus in order to allow other sessions play seamlessly. The state of playback should be
1058          * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
1059          * of the prepared content should start in the implementation of {@link #onPlay}. Override
1060          * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
1061          */
onPrepareFromSearch(String query, Bundle extras)1062         public void onPrepareFromSearch(String query, Bundle extras) {
1063         }
1064 
1065         /**
1066          * Override to handle requests to prepare a specific media item represented by a URI.
1067          * During the preparation, a session should not hold audio focus in order to allow
1068          * other sessions play seamlessly. The state of playback should be updated to
1069          * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
1070          * The playback of the prepared content should start in the implementation of
1071          * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
1072          * for starting playback without preparation.
1073          */
onPrepareFromUri(Uri uri, Bundle extras)1074         public void onPrepareFromUri(Uri uri, Bundle extras) {
1075         }
1076 
1077         /**
1078          * Override to handle requests to begin playback.
1079          */
onPlay()1080         public void onPlay() {
1081         }
1082 
1083         /**
1084          * Override to handle requests to begin playback from a search query. An
1085          * empty query indicates that the app may play any music. The
1086          * implementation should attempt to make a smart choice about what to
1087          * play.
1088          */
onPlayFromSearch(String query, Bundle extras)1089         public void onPlayFromSearch(String query, Bundle extras) {
1090         }
1091 
1092         /**
1093          * Override to handle requests to play a specific mediaId that was
1094          * provided by your app's {@link MediaBrowserService}.
1095          */
onPlayFromMediaId(String mediaId, Bundle extras)1096         public void onPlayFromMediaId(String mediaId, Bundle extras) {
1097         }
1098 
1099         /**
1100          * Override to handle requests to play a specific media item represented by a URI.
1101          */
onPlayFromUri(Uri uri, Bundle extras)1102         public void onPlayFromUri(Uri uri, Bundle extras) {
1103         }
1104 
1105         /**
1106          * Override to handle requests to play an item with a given id from the
1107          * play queue.
1108          */
onSkipToQueueItem(long id)1109         public void onSkipToQueueItem(long id) {
1110         }
1111 
1112         /**
1113          * Override to handle requests to pause playback.
1114          */
onPause()1115         public void onPause() {
1116         }
1117 
1118         /**
1119          * Override to handle requests to skip to the next media item.
1120          */
onSkipToNext()1121         public void onSkipToNext() {
1122         }
1123 
1124         /**
1125          * Override to handle requests to skip to the previous media item.
1126          */
onSkipToPrevious()1127         public void onSkipToPrevious() {
1128         }
1129 
1130         /**
1131          * Override to handle requests to fast forward.
1132          */
onFastForward()1133         public void onFastForward() {
1134         }
1135 
1136         /**
1137          * Override to handle requests to rewind.
1138          */
onRewind()1139         public void onRewind() {
1140         }
1141 
1142         /**
1143          * Override to handle requests to stop playback.
1144          */
onStop()1145         public void onStop() {
1146         }
1147 
1148         /**
1149          * Override to handle requests to seek to a specific position in ms.
1150          *
1151          * @param pos New position to move to, in milliseconds.
1152          */
onSeekTo(long pos)1153         public void onSeekTo(long pos) {
1154         }
1155 
1156         /**
1157          * Override to handle the item being rated.
1158          *
1159          * @param rating
1160          */
onSetRating(@onNull Rating rating)1161         public void onSetRating(@NonNull Rating rating) {
1162         }
1163 
1164         /**
1165          * Override to handle the playback speed change.
1166          * To update the new playback speed, create a new {@link PlaybackState} by using {@link
1167          * PlaybackState.Builder#setState(int, long, float)}, and set it with
1168          * {@link #setPlaybackState(PlaybackState)}.
1169          * <p>
1170          * A value of {@code 1.0f} is the default playback value, and a negative value indicates
1171          * reverse playback. The {@code speed} will not be equal to zero.
1172          *
1173          * @param speed the playback speed
1174          * @see #setPlaybackState(PlaybackState)
1175          * @see PlaybackState.Builder#setState(int, long, float)
1176          */
onSetPlaybackSpeed(float speed)1177         public void onSetPlaybackSpeed(float speed) {
1178         }
1179 
1180         /**
1181          * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
1182          * performed.
1183          *
1184          * @param action The action that was originally sent in the
1185          *               {@link PlaybackState.CustomAction}.
1186          * @param extras Optional extras specified by the {@link MediaController}.
1187          */
onCustomAction(@onNull String action, @Nullable Bundle extras)1188         public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
1189         }
1190     }
1191 
1192     /**
1193      * @hide
1194      */
1195     public static class CallbackStub extends ISessionCallback.Stub {
1196         private WeakReference<MediaSession> mMediaSession;
1197 
CallbackStub(MediaSession session)1198         public CallbackStub(MediaSession session) {
1199             mMediaSession = new WeakReference<>(session);
1200         }
1201 
createRemoteUserInfo(String packageName, int pid, int uid)1202         private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) {
1203             return new RemoteUserInfo(packageName, pid, uid);
1204         }
1205 
1206         @Override
onCommand(String packageName, int pid, int uid, String command, Bundle args, ResultReceiver cb)1207         public void onCommand(String packageName, int pid, int uid, String command, Bundle args,
1208                 ResultReceiver cb) {
1209             MediaSession session = mMediaSession.get();
1210             if (session != null) {
1211                 session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
1212                         command, args, cb);
1213             }
1214         }
1215 
1216         @Override
onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)1217         public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
1218                 int sequenceNumber, ResultReceiver cb) {
1219             MediaSession session = mMediaSession.get();
1220             try {
1221                 if (session != null) {
1222                     session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
1223                             mediaButtonIntent);
1224                 }
1225             } finally {
1226                 if (cb != null) {
1227                     cb.send(sequenceNumber, null);
1228                 }
1229             }
1230         }
1231 
1232         @Override
onMediaButtonFromController(String packageName, int pid, int uid, Intent mediaButtonIntent)1233         public void onMediaButtonFromController(String packageName, int pid, int uid,
1234                 Intent mediaButtonIntent) {
1235             MediaSession session = mMediaSession.get();
1236             if (session != null) {
1237                 session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
1238                         mediaButtonIntent);
1239             }
1240         }
1241 
1242         @Override
onPrepare(String packageName, int pid, int uid)1243         public void onPrepare(String packageName, int pid, int uid) {
1244             MediaSession session = mMediaSession.get();
1245             if (session != null) {
1246                 session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid));
1247             }
1248         }
1249 
1250         @Override
onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId, Bundle extras)1251         public void onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId,
1252                 Bundle extras) {
1253             MediaSession session = mMediaSession.get();
1254             if (session != null) {
1255                 session.dispatchPrepareFromMediaId(
1256                         createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
1257             }
1258         }
1259 
1260         @Override
onPrepareFromSearch(String packageName, int pid, int uid, String query, Bundle extras)1261         public void onPrepareFromSearch(String packageName, int pid, int uid, String query,
1262                 Bundle extras) {
1263             MediaSession session = mMediaSession.get();
1264             if (session != null) {
1265                 session.dispatchPrepareFromSearch(
1266                         createRemoteUserInfo(packageName, pid, uid), query, extras);
1267             }
1268         }
1269 
1270         @Override
onPrepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras)1271         public void onPrepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
1272             MediaSession session = mMediaSession.get();
1273             if (session != null) {
1274                 session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid),
1275                         uri, extras);
1276             }
1277         }
1278 
1279         @Override
onPlay(String packageName, int pid, int uid)1280         public void onPlay(String packageName, int pid, int uid) {
1281             MediaSession session = mMediaSession.get();
1282             if (session != null) {
1283                 session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
1284             }
1285         }
1286 
1287         @Override
onPlayFromMediaId(String packageName, int pid, int uid, String mediaId, Bundle extras)1288         public void onPlayFromMediaId(String packageName, int pid, int uid, String mediaId,
1289                 Bundle extras) {
1290             MediaSession session = mMediaSession.get();
1291             if (session != null) {
1292                 session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid),
1293                         mediaId, extras);
1294             }
1295         }
1296 
1297         @Override
onPlayFromSearch(String packageName, int pid, int uid, String query, Bundle extras)1298         public void onPlayFromSearch(String packageName, int pid, int uid, String query,
1299                 Bundle extras) {
1300             MediaSession session = mMediaSession.get();
1301             if (session != null) {
1302                 session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid),
1303                         query, extras);
1304             }
1305         }
1306 
1307         @Override
onPlayFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras)1308         public void onPlayFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
1309             MediaSession session = mMediaSession.get();
1310             if (session != null) {
1311                 session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid),
1312                         uri, extras);
1313             }
1314         }
1315 
1316         @Override
onSkipToTrack(String packageName, int pid, int uid, long id)1317         public void onSkipToTrack(String packageName, int pid, int uid, long id) {
1318             MediaSession session = mMediaSession.get();
1319             if (session != null) {
1320                 session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid), id);
1321             }
1322         }
1323 
1324         @Override
onPause(String packageName, int pid, int uid)1325         public void onPause(String packageName, int pid, int uid) {
1326             MediaSession session = mMediaSession.get();
1327             if (session != null) {
1328                 session.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
1329             }
1330         }
1331 
1332         @Override
onStop(String packageName, int pid, int uid)1333         public void onStop(String packageName, int pid, int uid) {
1334             MediaSession session = mMediaSession.get();
1335             if (session != null) {
1336                 session.dispatchStop(createRemoteUserInfo(packageName, pid, uid));
1337             }
1338         }
1339 
1340         @Override
onNext(String packageName, int pid, int uid)1341         public void onNext(String packageName, int pid, int uid) {
1342             MediaSession session = mMediaSession.get();
1343             if (session != null) {
1344                 session.dispatchNext(createRemoteUserInfo(packageName, pid, uid));
1345             }
1346         }
1347 
1348         @Override
onPrevious(String packageName, int pid, int uid)1349         public void onPrevious(String packageName, int pid, int uid) {
1350             MediaSession session = mMediaSession.get();
1351             if (session != null) {
1352                 session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid));
1353             }
1354         }
1355 
1356         @Override
onFastForward(String packageName, int pid, int uid)1357         public void onFastForward(String packageName, int pid, int uid) {
1358             MediaSession session = mMediaSession.get();
1359             if (session != null) {
1360                 session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid));
1361             }
1362         }
1363 
1364         @Override
onRewind(String packageName, int pid, int uid)1365         public void onRewind(String packageName, int pid, int uid) {
1366             MediaSession session = mMediaSession.get();
1367             if (session != null) {
1368                 session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid));
1369             }
1370         }
1371 
1372         @Override
onSeekTo(String packageName, int pid, int uid, long pos)1373         public void onSeekTo(String packageName, int pid, int uid, long pos) {
1374             MediaSession session = mMediaSession.get();
1375             if (session != null) {
1376                 session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid), pos);
1377             }
1378         }
1379 
1380         @Override
onRate(String packageName, int pid, int uid, Rating rating)1381         public void onRate(String packageName, int pid, int uid, Rating rating) {
1382             MediaSession session = mMediaSession.get();
1383             if (session != null) {
1384                 session.dispatchRate(createRemoteUserInfo(packageName, pid, uid), rating);
1385             }
1386         }
1387 
1388         @Override
onSetPlaybackSpeed(String packageName, int pid, int uid, float speed)1389         public void onSetPlaybackSpeed(String packageName, int pid, int uid, float speed) {
1390             MediaSession session = mMediaSession.get();
1391             if (session != null) {
1392                 session.dispatchSetPlaybackSpeed(
1393                         createRemoteUserInfo(packageName, pid, uid), speed);
1394             }
1395         }
1396 
1397         @Override
onCustomAction(String packageName, int pid, int uid, String action, Bundle args)1398         public void onCustomAction(String packageName, int pid, int uid, String action,
1399                 Bundle args) {
1400             MediaSession session = mMediaSession.get();
1401             if (session != null) {
1402                 session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid),
1403                         action, args);
1404             }
1405         }
1406 
1407         @Override
onAdjustVolume(String packageName, int pid, int uid, int direction)1408         public void onAdjustVolume(String packageName, int pid, int uid, int direction) {
1409             MediaSession session = mMediaSession.get();
1410             if (session != null) {
1411                 session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid),
1412                         direction);
1413             }
1414         }
1415 
1416         @Override
onSetVolumeTo(String packageName, int pid, int uid, int value)1417         public void onSetVolumeTo(String packageName, int pid, int uid, int value) {
1418             MediaSession session = mMediaSession.get();
1419             if (session != null) {
1420                 session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid),
1421                         value);
1422             }
1423         }
1424     }
1425 
1426     /**
1427      * A single item that is part of the play queue. It contains a description
1428      * of the item and its id in the queue.
1429      */
1430     public static final class QueueItem implements Parcelable {
1431         /**
1432          * This id is reserved. No items can be explicitly assigned this id.
1433          */
1434         public static final int UNKNOWN_ID = -1;
1435 
1436         private final MediaDescription mDescription;
1437         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1438         private final long mId;
1439 
1440         /**
1441          * Create a new {@link MediaSession.QueueItem}.
1442          *
1443          * @param description The {@link MediaDescription} for this item.
1444          * @param id An identifier for this item. It must be unique within the
1445          *            play queue and cannot be {@link #UNKNOWN_ID}.
1446          */
QueueItem(MediaDescription description, long id)1447         public QueueItem(MediaDescription description, long id) {
1448             if (description == null) {
1449                 throw new IllegalArgumentException("Description cannot be null.");
1450             }
1451             if (id == UNKNOWN_ID) {
1452                 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1453             }
1454             mDescription = description;
1455             mId = id;
1456         }
1457 
QueueItem(Parcel in)1458         private QueueItem(Parcel in) {
1459             mDescription = MediaDescription.CREATOR.createFromParcel(in);
1460             mId = in.readLong();
1461         }
1462 
1463         /**
1464          * Get the description for this item.
1465          */
getDescription()1466         public MediaDescription getDescription() {
1467             return mDescription;
1468         }
1469 
1470         /**
1471          * Get the queue id for this item.
1472          */
getQueueId()1473         public long getQueueId() {
1474             return mId;
1475         }
1476 
1477         @Override
writeToParcel(Parcel dest, int flags)1478         public void writeToParcel(Parcel dest, int flags) {
1479             mDescription.writeToParcel(dest, flags);
1480             dest.writeLong(mId);
1481         }
1482 
1483         @Override
describeContents()1484         public int describeContents() {
1485             return 0;
1486         }
1487 
1488         public static final @android.annotation.NonNull Creator<MediaSession.QueueItem> CREATOR =
1489                 new Creator<MediaSession.QueueItem>() {
1490 
1491                     @Override
1492                     public MediaSession.QueueItem createFromParcel(Parcel p) {
1493                         return new MediaSession.QueueItem(p);
1494                     }
1495 
1496                     @Override
1497                     public MediaSession.QueueItem[] newArray(int size) {
1498                         return new MediaSession.QueueItem[size];
1499                     }
1500                 };
1501 
1502         @Override
toString()1503         public String toString() {
1504             return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId
1505                     + " }";
1506         }
1507 
1508         @Override
equals(Object o)1509         public boolean equals(Object o) {
1510             if (o == null) {
1511                 return false;
1512             }
1513 
1514             if (!(o instanceof QueueItem)) {
1515                 return false;
1516             }
1517 
1518             final QueueItem item = (QueueItem) o;
1519             if (mId != item.mId) {
1520                 return false;
1521             }
1522 
1523             if (!Objects.equals(mDescription, item.mDescription)) {
1524                 return false;
1525             }
1526 
1527             return true;
1528         }
1529     }
1530 
1531     private static final class Command {
1532         public final String command;
1533         public final Bundle extras;
1534         public final ResultReceiver stub;
1535 
Command(String command, Bundle extras, ResultReceiver stub)1536         Command(String command, Bundle extras, ResultReceiver stub) {
1537             this.command = command;
1538             this.extras = extras;
1539             this.stub = stub;
1540         }
1541     }
1542 
1543     private class CallbackMessageHandler extends Handler {
1544         private static final int MSG_COMMAND = 1;
1545         private static final int MSG_MEDIA_BUTTON = 2;
1546         private static final int MSG_PREPARE = 3;
1547         private static final int MSG_PREPARE_MEDIA_ID = 4;
1548         private static final int MSG_PREPARE_SEARCH = 5;
1549         private static final int MSG_PREPARE_URI = 6;
1550         private static final int MSG_PLAY = 7;
1551         private static final int MSG_PLAY_MEDIA_ID = 8;
1552         private static final int MSG_PLAY_SEARCH = 9;
1553         private static final int MSG_PLAY_URI = 10;
1554         private static final int MSG_SKIP_TO_ITEM = 11;
1555         private static final int MSG_PAUSE = 12;
1556         private static final int MSG_STOP = 13;
1557         private static final int MSG_NEXT = 14;
1558         private static final int MSG_PREVIOUS = 15;
1559         private static final int MSG_FAST_FORWARD = 16;
1560         private static final int MSG_REWIND = 17;
1561         private static final int MSG_SEEK_TO = 18;
1562         private static final int MSG_RATE = 19;
1563         private static final int MSG_SET_PLAYBACK_SPEED = 20;
1564         private static final int MSG_CUSTOM_ACTION = 21;
1565         private static final int MSG_ADJUST_VOLUME = 22;
1566         private static final int MSG_SET_VOLUME = 23;
1567         private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 24;
1568 
1569         private MediaSession.Callback mCallback;
1570         private RemoteUserInfo mCurrentControllerInfo;
1571 
CallbackMessageHandler(Looper looper, MediaSession.Callback callback)1572         CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
1573             super(looper);
1574             mCallback = callback;
1575             mCallback.mHandler = this;
1576         }
1577 
post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs)1578         void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
1579             Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
1580             Message msg = obtainMessage(what, objWithCaller);
1581             msg.setAsynchronous(true);
1582             msg.setData(data);
1583             if (delayMs > 0) {
1584                 sendMessageDelayed(msg, delayMs);
1585             } else {
1586                 sendMessage(msg);
1587             }
1588         }
1589 
1590         @Override
handleMessage(Message msg)1591         public void handleMessage(Message msg) {
1592             mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
1593 
1594             VolumeProvider vp;
1595             Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
1596 
1597             switch (msg.what) {
1598                 case MSG_COMMAND:
1599                     Command cmd = (Command) obj;
1600                     mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
1601                     break;
1602                 case MSG_MEDIA_BUTTON:
1603                     mCallback.onMediaButtonEvent((Intent) obj);
1604                     break;
1605                 case MSG_PREPARE:
1606                     mCallback.onPrepare();
1607                     break;
1608                 case MSG_PREPARE_MEDIA_ID:
1609                     mCallback.onPrepareFromMediaId((String) obj, msg.getData());
1610                     break;
1611                 case MSG_PREPARE_SEARCH:
1612                     mCallback.onPrepareFromSearch((String) obj, msg.getData());
1613                     break;
1614                 case MSG_PREPARE_URI:
1615                     mCallback.onPrepareFromUri((Uri) obj, msg.getData());
1616                     break;
1617                 case MSG_PLAY:
1618                     mCallback.onPlay();
1619                     break;
1620                 case MSG_PLAY_MEDIA_ID:
1621                     mCallback.onPlayFromMediaId((String) obj, msg.getData());
1622                     break;
1623                 case MSG_PLAY_SEARCH:
1624                     mCallback.onPlayFromSearch((String) obj, msg.getData());
1625                     break;
1626                 case MSG_PLAY_URI:
1627                     mCallback.onPlayFromUri((Uri) obj, msg.getData());
1628                     break;
1629                 case MSG_SKIP_TO_ITEM:
1630                     mCallback.onSkipToQueueItem((Long) obj);
1631                     break;
1632                 case MSG_PAUSE:
1633                     mCallback.onPause();
1634                     break;
1635                 case MSG_STOP:
1636                     mCallback.onStop();
1637                     break;
1638                 case MSG_NEXT:
1639                     mCallback.onSkipToNext();
1640                     break;
1641                 case MSG_PREVIOUS:
1642                     mCallback.onSkipToPrevious();
1643                     break;
1644                 case MSG_FAST_FORWARD:
1645                     mCallback.onFastForward();
1646                     break;
1647                 case MSG_REWIND:
1648                     mCallback.onRewind();
1649                     break;
1650                 case MSG_SEEK_TO:
1651                     mCallback.onSeekTo((Long) obj);
1652                     break;
1653                 case MSG_RATE:
1654                     mCallback.onSetRating((Rating) obj);
1655                     break;
1656                 case MSG_SET_PLAYBACK_SPEED:
1657                     mCallback.onSetPlaybackSpeed((Float) obj);
1658                     break;
1659                 case MSG_CUSTOM_ACTION:
1660                     mCallback.onCustomAction((String) obj, msg.getData());
1661                     break;
1662                 case MSG_ADJUST_VOLUME:
1663                     synchronized (mLock) {
1664                         vp = mVolumeProvider;
1665                     }
1666                     if (vp != null) {
1667                         vp.onAdjustVolume((int) obj);
1668                     }
1669                     break;
1670                 case MSG_SET_VOLUME:
1671                     synchronized (mLock) {
1672                         vp = mVolumeProvider;
1673                     }
1674                     if (vp != null) {
1675                         vp.onSetVolumeTo((int) obj);
1676                     }
1677                     break;
1678                 case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
1679                     mCallback.handleMediaPlayPauseKeySingleTapIfPending();
1680                     break;
1681             }
1682             mCurrentControllerInfo = null;
1683         }
1684     }
1685 }
1686