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