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