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