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