• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright (C) 2014 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package android.support.v4.media.session;
19 
20 import android.app.Activity;
21 import android.app.PendingIntent;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Bitmap;
27 import android.media.AudioManager;
28 import android.net.Uri;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.Parcel;
36 import android.os.Parcelable;
37 import android.os.RemoteCallbackList;
38 import android.os.RemoteException;
39 import android.os.ResultReceiver;
40 import android.os.SystemClock;
41 import android.support.annotation.IntDef;
42 import android.support.v4.media.MediaDescriptionCompat;
43 import android.support.v4.media.MediaMetadataCompat;
44 import android.support.v4.media.RatingCompat;
45 import android.support.v4.media.VolumeProviderCompat;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.TypedValue;
49 import android.view.KeyEvent;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 /**
57  * Allows interaction with media controllers, volume keys, media buttons, and
58  * transport controls.
59  * <p>
60  * A MediaSession should be created when an app wants to publish media playback
61  * information or handle media keys. In general an app only needs one session
62  * for all playback, though multiple sessions can be created to provide finer
63  * grain controls of media.
64  * <p>
65  * Once a session is created the owner of the session may pass its
66  * {@link #getSessionToken() session token} to other processes to allow them to
67  * create a {@link MediaControllerCompat} to interact with the session.
68  * <p>
69  * To receive commands, media keys, and other events a {@link Callback} must be
70  * set with {@link #setCallback(Callback)}.
71  * <p>
72  * When an app is finished performing playback it must call {@link #release()}
73  * to clean up the session and notify any controllers.
74  * <p>
75  * MediaSessionCompat objects are not thread safe and all calls should be made
76  * from the same thread.
77  * <p>
78  * This is a helper for accessing features in
79  * {@link android.media.session.MediaSession} introduced after API level 4 in a
80  * backwards compatible fashion.
81  */
82 public class MediaSessionCompat {
83     private static final String TAG = "MediaSessionCompat";
84 
85     private final MediaSessionImpl mImpl;
86     private final MediaControllerCompat mController;
87     private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
88 
89     /**
90      * @hide
91      */
92     @IntDef(flag=true, value={FLAG_HANDLES_MEDIA_BUTTONS, FLAG_HANDLES_TRANSPORT_CONTROLS})
93     @Retention(RetentionPolicy.SOURCE)
94     public @interface SessionFlags {}
95 
96     /**
97      * Set this flag on the session to indicate that it can handle media button
98      * events.
99      */
100     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
101 
102     /**
103      * Set this flag on the session to indicate that it handles transport
104      * control commands through its {@link Callback}.
105      */
106     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
107 
108     /**
109      * Custom action to invoke playFromUri() for the forward compatibility.
110      */
111     static final String ACTION_PLAY_FROM_URI =
112             "android.support.v4.media.session.action.PLAY_FROM_URI";
113 
114     /**
115      * Custom action to invoke prepare() for the forward compatibility.
116      */
117     static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE";
118 
119     /**
120      * Custom action to invoke prepareFromMediaId() for the forward compatibility.
121      */
122     static final String ACTION_PREPARE_FROM_MEDIA_ID =
123             "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID";
124 
125     /**
126      * Custom action to invoke prepareFromSearch() for the forward compatibility.
127      */
128     static final String ACTION_PREPARE_FROM_SEARCH =
129             "android.support.v4.media.session.action.PREPARE_FROM_SEARCH";
130 
131     /**
132      * Custom action to invoke prepareFromUri() for the forward compatibility.
133      */
134     static final String ACTION_PREPARE_FROM_URI =
135             "android.support.v4.media.session.action.PREPARE_FROM_URI";
136 
137     /**
138      * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
139      */
140     static final String ACTION_ARGUMENT_MEDIA_ID =
141             "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID";
142 
143     /**
144      * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query.
145      */
146     static final String ACTION_ARGUMENT_QUERY =
147             "android.support.v4.media.session.action.ARGUMENT_QUERY";
148 
149     /**
150      * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI}
151      * indicating URI to play.
152      */
153     static final String ACTION_ARGUMENT_URI =
154             "android.support.v4.media.session.action.ARGUMENT_URI";
155 
156     /**
157      * Argument for use with various actions indicating extra bundle.
158      */
159     static final String ACTION_ARGUMENT_EXTRAS =
160             "android.support.v4.media.session.action.ARGUMENT_EXTRAS";
161 
162     // Maximum size of the bitmap in dp.
163     private static final int MAX_BITMAP_SIZE_IN_DP = 320;
164 
165     // Maximum size of the bitmap in px. It shouldn't be changed.
166     private static int sMaxBitmapSize;
167 
168     /**
169      * Creates a new session. You must call {@link #release()} when finished with the session.
170      * <p>
171      * The session will automatically be registered with the system but will not be published
172      * until {@link #setActive(boolean) setActive(true)} is called.
173      * </p><p>
174      * For API 20 or earlier, note that a media button receiver is required for handling
175      * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate
176      * {@link BroadcastReceiver} from your manifest. See {@link MediaButtonReceiver} for more
177      * details.
178      * </p>
179      * @param context The context to use to create the session.
180      * @param tag A short name for debugging purposes.
181      */
MediaSessionCompat(Context context, String tag)182     public MediaSessionCompat(Context context, String tag) {
183         this(context, tag, null, null);
184     }
185 
186     /**
187      * Creates a new session with a specified media button receiver (a component name and/or
188      * a pending intent). You must call {@link #release()} when finished with the session.
189      * <p>
190      * The session will automatically be registered with the system but will not be published
191      * until {@link #setActive(boolean) setActive(true)} is called. Note that {@code mbrComponent}
192      * and {@code mrbIntent} are only used for API 20 or earlier. If you  want to set a media button
193      * receiver in API 21 or later, call {@link #setMediaButtonReceiver}.
194      * </p><p>
195      * For API 20 or earlier, the new session will use the given {@code mbrComponent}.
196      * If null, this will attempt to find an appropriate {@link BroadcastReceiver} that handles
197      * {@link Intent#ACTION_MEDIA_BUTTON} from your manifest. See {@link MediaButtonReceiver} for
198      * more details.
199      * </p>
200      * @param context The context to use to create the session.
201      * @param tag A short name for debugging purposes.
202      * @param mbrComponent The component name for your media button receiver.
203      * @param mbrIntent The PendingIntent for your receiver component that handles
204      *            media button events. This is optional and will be used on between
205      *            {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
206      *            {@link android.os.Build.VERSION_CODES#KITKAT_WATCH} instead of the
207      *            component name.
208      */
MediaSessionCompat(Context context, String tag, ComponentName mbrComponent, PendingIntent mbrIntent)209     public MediaSessionCompat(Context context, String tag, ComponentName mbrComponent,
210             PendingIntent mbrIntent) {
211         if (context == null) {
212             throw new IllegalArgumentException("context must not be null");
213         }
214         if (TextUtils.isEmpty(tag)) {
215             throw new IllegalArgumentException("tag must not be null or empty");
216         }
217 
218         if (android.os.Build.VERSION.SDK_INT >= 21) {
219             mImpl = new MediaSessionImplApi21(context, tag);
220         } else {
221             mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent);
222         }
223         mController = new MediaControllerCompat(context, this);
224 
225         if (sMaxBitmapSize == 0) {
226             sMaxBitmapSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
227                     MAX_BITMAP_SIZE_IN_DP, context.getResources().getDisplayMetrics());
228         }
229     }
230 
MediaSessionCompat(Context context, MediaSessionImpl impl)231     private MediaSessionCompat(Context context, MediaSessionImpl impl) {
232         mImpl = impl;
233         mController = new MediaControllerCompat(context, this);
234     }
235 
236     /**
237      * Add a callback to receive updates on for the MediaSession. This includes
238      * media button and volume events. The caller's thread will be used to post
239      * events.
240      *
241      * @param callback The callback object
242      */
setCallback(Callback callback)243     public void setCallback(Callback callback) {
244         setCallback(callback, null);
245     }
246 
247     /**
248      * Set the callback to receive updates for the MediaSession. This includes
249      * media button and volume events. Set the callback to null to stop
250      * receiving events.
251      *
252      * @param callback The callback to receive updates on.
253      * @param handler The handler that events should be posted on.
254      */
setCallback(Callback callback, Handler handler)255     public void setCallback(Callback callback, Handler handler) {
256         mImpl.setCallback(callback, handler != null ? handler : new Handler());
257     }
258 
259     /**
260      * Set an intent for launching UI for this Session. This can be used as a
261      * quick link to an ongoing media screen. The intent should be for an
262      * activity that may be started using
263      * {@link Activity#startActivity(Intent)}.
264      *
265      * @param pi The intent to launch to show UI for this Session.
266      */
setSessionActivity(PendingIntent pi)267     public void setSessionActivity(PendingIntent pi) {
268         mImpl.setSessionActivity(pi);
269     }
270 
271     /**
272      * Set a pending intent for your media button receiver to allow restarting
273      * playback after the session has been stopped. If your app is started in
274      * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
275      * the pending intent.
276      * <p>
277      * This method will only work on
278      * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
279      * platform versions must include the media button receiver in the
280      * constructor.
281      *
282      * @param mbr The {@link PendingIntent} to send the media button event to.
283      */
setMediaButtonReceiver(PendingIntent mbr)284     public void setMediaButtonReceiver(PendingIntent mbr) {
285         mImpl.setMediaButtonReceiver(mbr);
286     }
287 
288     /**
289      * Set any flags for the session.
290      *
291      * @param flags The flags to set for this session.
292      */
setFlags(@essionFlags int flags)293     public void setFlags(@SessionFlags int flags) {
294         mImpl.setFlags(flags);
295     }
296 
297     /**
298      * Set the stream this session is playing on. This will affect the system's
299      * volume handling for this session. If {@link #setPlaybackToRemote} was
300      * previously called it will stop receiving volume commands and the system
301      * will begin sending volume changes to the appropriate stream.
302      * <p>
303      * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
304      *
305      * @param stream The {@link AudioManager} stream this session is playing on.
306      */
setPlaybackToLocal(int stream)307     public void setPlaybackToLocal(int stream) {
308         mImpl.setPlaybackToLocal(stream);
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      * current stream volume for this session. If {@link #setPlaybackToLocal}
315      * was previously called that stream will stop receiving volume changes for
316      * this session.
317      * <p>
318      * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
319      * this will only allow an app to handle volume commands sent directly to
320      * the session by a {@link MediaControllerCompat}. System routing of volume
321      * keys will not use the volume provider.
322      *
323      * @param volumeProvider The provider that will handle volume changes. May
324      *            not be null.
325      */
setPlaybackToRemote(VolumeProviderCompat volumeProvider)326     public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
327         if (volumeProvider == null) {
328             throw new IllegalArgumentException("volumeProvider may not be null!");
329         }
330         mImpl.setPlaybackToRemote(volumeProvider);
331     }
332 
333     /**
334      * Set if this session is currently active and ready to receive commands. If
335      * set to false your session's controller may not be discoverable. You must
336      * set the session to active before it can start receiving media button
337      * events or transport commands.
338      * <p>
339      * On platforms earlier than
340      * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
341      * a media button event receiver should be set via the constructor to
342      * receive media button events.
343      *
344      * @param active Whether this session is active or not.
345      */
setActive(boolean active)346     public void setActive(boolean active) {
347         mImpl.setActive(active);
348         for (OnActiveChangeListener listener : mActiveListeners) {
349             listener.onActiveChanged();
350         }
351     }
352 
353     /**
354      * Get the current active state of this session.
355      *
356      * @return True if the session is active, false otherwise.
357      */
isActive()358     public boolean isActive() {
359         return mImpl.isActive();
360     }
361 
362     /**
363      * Send a proprietary event to all MediaControllers listening to this
364      * Session. It's up to the Controller/Session owner to determine the meaning
365      * of any events.
366      *
367      * @param event The name of the event to send
368      * @param extras Any extras included with the event
369      */
sendSessionEvent(String event, Bundle extras)370     public void sendSessionEvent(String event, Bundle extras) {
371         if (TextUtils.isEmpty(event)) {
372             throw new IllegalArgumentException("event cannot be null or empty");
373         }
374         mImpl.sendSessionEvent(event, extras);
375     }
376 
377     /**
378      * This must be called when an app has finished performing playback. If
379      * playback is expected to start again shortly the session can be left open,
380      * but it must be released if your activity or service is being destroyed.
381      */
release()382     public void release() {
383         mImpl.release();
384     }
385 
386     /**
387      * Retrieve a token object that can be used by apps to create a
388      * {@link MediaControllerCompat} for interacting with this session. The
389      * owner of the session is responsible for deciding how to distribute these
390      * tokens.
391      * <p>
392      * On platform versions before
393      * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
394      * used within your app as there is no way to guarantee other apps are using
395      * the same version of the support library.
396      *
397      * @return A token that can be used to create a media controller for this
398      *         session.
399      */
getSessionToken()400     public Token getSessionToken() {
401         return mImpl.getSessionToken();
402     }
403 
404     /**
405      * Get a controller for this session. This is a convenience method to avoid
406      * having to cache your own controller in process.
407      *
408      * @return A controller for this session.
409      */
getController()410     public MediaControllerCompat getController() {
411         return mController;
412     }
413 
414     /**
415      * Update the current playback state.
416      *
417      * @param state The current state of playback
418      */
setPlaybackState(PlaybackStateCompat state)419     public void setPlaybackState(PlaybackStateCompat state) {
420         mImpl.setPlaybackState(state);
421     }
422 
423     /**
424      * Update the current metadata. New metadata can be created using
425      * {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time
426      * proportional to the size of the bitmap to replace large bitmaps with a scaled down copy.
427      *
428      * @param metadata The new metadata
429      * @see android.support.v4.media.MediaMetadataCompat.Builder#putBitmap
430      */
setMetadata(MediaMetadataCompat metadata)431     public void setMetadata(MediaMetadataCompat metadata) {
432         mImpl.setMetadata(metadata);
433     }
434 
435     /**
436      * Update the list of items in the play queue. It is an ordered list and
437      * should contain the current item, and previous or upcoming items if they
438      * exist. Specify null if there is no current play queue.
439      * <p>
440      * The queue should be of reasonable size. If the play queue is unbounded
441      * within your app, it is better to send a reasonable amount in a sliding
442      * window instead.
443      *
444      * @param queue A list of items in the play queue.
445      */
setQueue(List<QueueItem> queue)446     public void setQueue(List<QueueItem> queue) {
447         mImpl.setQueue(queue);
448     }
449 
450     /**
451      * Set the title of the play queue. The UI should display this title along
452      * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
453      * name.
454      *
455      * @param title The title of the play queue.
456      */
setQueueTitle(CharSequence title)457     public void setQueueTitle(CharSequence title) {
458         mImpl.setQueueTitle(title);
459     }
460 
461     /**
462      * Set the style of rating used by this session. Apps trying to set the
463      * rating should use this style. Must be one of the following:
464      * <ul>
465      * <li>{@link RatingCompat#RATING_NONE}</li>
466      * <li>{@link RatingCompat#RATING_3_STARS}</li>
467      * <li>{@link RatingCompat#RATING_4_STARS}</li>
468      * <li>{@link RatingCompat#RATING_5_STARS}</li>
469      * <li>{@link RatingCompat#RATING_HEART}</li>
470      * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
471      * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
472      * </ul>
473      */
setRatingType(@atingCompat.Style int type)474     public void setRatingType(@RatingCompat.Style int type) {
475         mImpl.setRatingType(type);
476     }
477 
478     /**
479      * Set some extras that can be associated with the
480      * {@link MediaSessionCompat}. No assumptions should be made as to how a
481      * {@link MediaControllerCompat} will handle these extras. Keys should be
482      * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
483      *
484      * @param extras The extras associated with the session.
485      */
setExtras(Bundle extras)486     public void setExtras(Bundle extras) {
487         mImpl.setExtras(extras);
488     }
489 
490     /**
491      * Gets the underlying framework {@link android.media.session.MediaSession}
492      * object.
493      * <p>
494      * This method is only supported on API 21+.
495      * </p>
496      *
497      * @return The underlying {@link android.media.session.MediaSession} object,
498      *         or null if none.
499      */
getMediaSession()500     public Object getMediaSession() {
501         return mImpl.getMediaSession();
502     }
503 
504     /**
505      * Gets the underlying framework {@link android.media.RemoteControlClient}
506      * object.
507      * <p>
508      * This method is only supported on APIs 14-20. On API 21+
509      * {@link #getMediaSession()} should be used instead.
510      *
511      * @return The underlying {@link android.media.RemoteControlClient} object,
512      *         or null if none.
513      */
getRemoteControlClient()514     public Object getRemoteControlClient() {
515         return mImpl.getRemoteControlClient();
516     }
517 
518     /**
519      * Returns the name of the package that sent the last media button, transport control, or
520      * command from controllers and the system. This is only valid while in a request callback, such
521      * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices.
522      *
523      * @hide
524      */
getCallingPackage()525     public String getCallingPackage() {
526         return mImpl.getCallingPackage();
527     }
528 
529     /**
530      * Adds a listener to be notified when the active status of this session
531      * changes. This is primarily used by the support library and should not be
532      * needed by apps.
533      *
534      * @param listener The listener to add.
535      */
addOnActiveChangeListener(OnActiveChangeListener listener)536     public void addOnActiveChangeListener(OnActiveChangeListener listener) {
537         if (listener == null) {
538             throw new IllegalArgumentException("Listener may not be null");
539         }
540         mActiveListeners.add(listener);
541     }
542 
543     /**
544      * Stops the listener from being notified when the active status of this
545      * session changes.
546      *
547      * @param listener The listener to remove.
548      */
removeOnActiveChangeListener(OnActiveChangeListener listener)549     public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
550         if (listener == null) {
551             throw new IllegalArgumentException("Listener may not be null");
552         }
553         mActiveListeners.remove(listener);
554     }
555 
556     /**
557      * Creates an instance from a framework {@link android.media.session.MediaSession} object.
558      * <p>
559      * This method is only supported on API 21+. On API 20 and below, it returns null.
560      * </p>
561      *
562      * @param context The context to use to create the session.
563      * @param mediaSession A {@link android.media.session.MediaSession} object.
564      * @return An equivalent {@link MediaSessionCompat} object, or null if none.
565      * @deprecated Use {@link #fromMediaSession(Context, Object)} instead.
566      */
567     @Deprecated
obtain(Context context, Object mediaSession)568     public static MediaSessionCompat obtain(Context context, Object mediaSession) {
569         return fromMediaSession(context, mediaSession);
570     }
571 
572     /**
573      * Creates an instance from a framework {@link android.media.session.MediaSession} object.
574      * <p>
575      * This method is only supported on API 21+. On API 20 and below, it returns null.
576      * </p>
577      *
578      * @param context The context to use to create the session.
579      * @param mediaSession A {@link android.media.session.MediaSession} object.
580      * @return An equivalent {@link MediaSessionCompat} object, or null if none.
581      */
fromMediaSession(Context context, Object mediaSession)582     public static MediaSessionCompat fromMediaSession(Context context, Object mediaSession) {
583         if (context == null || mediaSession == null || Build.VERSION.SDK_INT < 21) {
584             return null;
585         }
586         return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
587     }
588 
589     /**
590      * Receives transport controls, media buttons, and commands from controllers
591      * and the system. The callback may be set using {@link #setCallback}.
592      */
593     public abstract static class Callback {
594         final Object mCallbackObj;
595 
Callback()596         public Callback() {
597             if (android.os.Build.VERSION.SDK_INT >= 24) {
598                 mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
599             } else if (android.os.Build.VERSION.SDK_INT >= 23) {
600                 mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
601             } else if (android.os.Build.VERSION.SDK_INT >= 21) {
602                 mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
603             } else {
604                 mCallbackObj = null;
605             }
606         }
607 
608         /**
609          * Called when a controller has sent a custom command to this session.
610          * The owner of the session may handle custom commands but is not
611          * required to.
612          *
613          * @param command The command name.
614          * @param extras Optional parameters for the command, may be null.
615          * @param cb A result receiver to which a result may be sent by the command, may be null.
616          */
onCommand(String command, Bundle extras, ResultReceiver cb)617         public void onCommand(String command, Bundle extras, ResultReceiver cb) {
618         }
619 
620         /**
621          * Override to handle media button events.
622          *
623          * @param mediaButtonEvent The media button event intent.
624          * @return True if the event was handled, false otherwise.
625          */
onMediaButtonEvent(Intent mediaButtonEvent)626         public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
627             return false;
628         }
629 
630         /**
631          * Override to handle requests to prepare playback. During the preparation, a session
632          * should not hold audio focus in order to allow other session play seamlessly.
633          * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED}
634          * after the preparation is done.
635          */
onPrepare()636         public void onPrepare() {
637         }
638 
639         /**
640          * Override to handle requests to prepare for playing a specific mediaId that was provided
641          * by your app. During the preparation, a session should not hold audio focus in order to
642          * allow other session play seamlessly. The state of playback should be updated to
643          * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback
644          * of the prepared content should start in the implementation of {@link #onPlay}. Override
645          * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
646          */
onPrepareFromMediaId(String mediaId, Bundle extras)647         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
648         }
649 
650         /**
651          * Override to handle requests to prepare playback from a search query. An
652          * empty query indicates that the app may prepare any music. The
653          * implementation should attempt to make a smart choice about what to
654          * play. During the preparation, a session should not hold audio focus in order to allow
655          * other session play seamlessly. The state of playback should be updated to
656          * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done.
657          * The playback of the prepared content should start in the implementation of
658          * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for
659          * starting playback without preparation.
660          */
onPrepareFromSearch(String query, Bundle extras)661         public void onPrepareFromSearch(String query, Bundle extras) {
662         }
663 
664         /**
665          * Override to handle requests to prepare a specific media item represented by a URI.
666          * During the preparation, a session should not hold audio focus in order to allow other
667          * session play seamlessly. The state of playback should be updated to
668          * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of
669          * the prepared content should start in the implementation of {@link #onPlay}. Override
670          * {@link #onPlayFromUri} to handle requests for starting playback without preparation.
671          */
onPrepareFromUri(Uri uri, Bundle extras)672         public void onPrepareFromUri(Uri uri, Bundle extras) {
673         }
674 
675         /**
676          * Override to handle requests to begin playback.
677          */
onPlay()678         public void onPlay() {
679         }
680 
681         /**
682          * Override to handle requests to play a specific mediaId that was
683          * provided by your app.
684          */
onPlayFromMediaId(String mediaId, Bundle extras)685         public void onPlayFromMediaId(String mediaId, Bundle extras) {
686         }
687 
688         /**
689          * Override to handle requests to begin playback from a search query. An
690          * empty query indicates that the app may play any music. The
691          * implementation should attempt to make a smart choice about what to
692          * play.
693          */
onPlayFromSearch(String query, Bundle extras)694         public void onPlayFromSearch(String query, Bundle extras) {
695         }
696 
697         /**
698          * Override to handle requests to play a specific media item represented by a URI.
699          */
onPlayFromUri(Uri uri, Bundle extras)700         public void onPlayFromUri(Uri uri, Bundle extras) {
701         }
702 
703         /**
704          * Override to handle requests to play an item with a given id from the
705          * play queue.
706          */
onSkipToQueueItem(long id)707         public void onSkipToQueueItem(long id) {
708         }
709 
710         /**
711          * Override to handle requests to pause playback.
712          */
onPause()713         public void onPause() {
714         }
715 
716         /**
717          * Override to handle requests to skip to the next media item.
718          */
onSkipToNext()719         public void onSkipToNext() {
720         }
721 
722         /**
723          * Override to handle requests to skip to the previous media item.
724          */
onSkipToPrevious()725         public void onSkipToPrevious() {
726         }
727 
728         /**
729          * Override to handle requests to fast forward.
730          */
onFastForward()731         public void onFastForward() {
732         }
733 
734         /**
735          * Override to handle requests to rewind.
736          */
onRewind()737         public void onRewind() {
738         }
739 
740         /**
741          * Override to handle requests to stop playback.
742          */
onStop()743         public void onStop() {
744         }
745 
746         /**
747          * Override to handle requests to seek to a specific position in ms.
748          *
749          * @param pos New position to move to, in milliseconds.
750          */
onSeekTo(long pos)751         public void onSeekTo(long pos) {
752         }
753 
754         /**
755          * Override to handle the item being rated.
756          *
757          * @param rating
758          */
onSetRating(RatingCompat rating)759         public void onSetRating(RatingCompat rating) {
760         }
761 
762         /**
763          * Called when a {@link MediaControllerCompat} wants a
764          * {@link PlaybackStateCompat.CustomAction} to be performed.
765          *
766          * @param action The action that was originally sent in the
767          *            {@link PlaybackStateCompat.CustomAction}.
768          * @param extras Optional extras specified by the
769          *            {@link MediaControllerCompat}.
770          */
onCustomAction(String action, Bundle extras)771         public void onCustomAction(String action, Bundle extras) {
772         }
773 
774         private class StubApi21 implements MediaSessionCompatApi21.Callback {
775 
776             @Override
onCommand(String command, Bundle extras, ResultReceiver cb)777             public void onCommand(String command, Bundle extras, ResultReceiver cb) {
778                 Callback.this.onCommand(command, extras, cb);
779             }
780 
781             @Override
onMediaButtonEvent(Intent mediaButtonIntent)782             public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
783                 return Callback.this.onMediaButtonEvent(mediaButtonIntent);
784             }
785 
786             @Override
onPlay()787             public void onPlay() {
788                 Callback.this.onPlay();
789             }
790 
791             @Override
onPlayFromMediaId(String mediaId, Bundle extras)792             public void onPlayFromMediaId(String mediaId, Bundle extras) {
793                 Callback.this.onPlayFromMediaId(mediaId, extras);
794             }
795 
796             @Override
onPlayFromSearch(String search, Bundle extras)797             public void onPlayFromSearch(String search, Bundle extras) {
798                 Callback.this.onPlayFromSearch(search, extras);
799             }
800 
801             @Override
onSkipToQueueItem(long id)802             public void onSkipToQueueItem(long id) {
803                 Callback.this.onSkipToQueueItem(id);
804             }
805 
806             @Override
onPause()807             public void onPause() {
808                 Callback.this.onPause();
809             }
810 
811             @Override
onSkipToNext()812             public void onSkipToNext() {
813                 Callback.this.onSkipToNext();
814             }
815 
816             @Override
onSkipToPrevious()817             public void onSkipToPrevious() {
818                 Callback.this.onSkipToPrevious();
819             }
820 
821             @Override
onFastForward()822             public void onFastForward() {
823                 Callback.this.onFastForward();
824             }
825 
826             @Override
onRewind()827             public void onRewind() {
828                 Callback.this.onRewind();
829             }
830 
831             @Override
onStop()832             public void onStop() {
833                 Callback.this.onStop();
834             }
835 
836             @Override
onSeekTo(long pos)837             public void onSeekTo(long pos) {
838                 Callback.this.onSeekTo(pos);
839             }
840 
841             @Override
onSetRating(Object ratingObj)842             public void onSetRating(Object ratingObj) {
843                 Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
844             }
845 
846             @Override
onCustomAction(String action, Bundle extras)847             public void onCustomAction(String action, Bundle extras) {
848                 if (action.equals(ACTION_PLAY_FROM_URI)) {
849                     Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
850                     Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
851                     Callback.this.onPlayFromUri(uri, bundle);
852                 } else if (action.equals(ACTION_PREPARE)) {
853                     Callback.this.onPrepare();
854                 } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
855                     String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
856                     Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
857                     Callback.this.onPrepareFromMediaId(mediaId, bundle);
858                 } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
859                     String query = extras.getString(ACTION_ARGUMENT_QUERY);
860                     Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
861                     Callback.this.onPrepareFromSearch(query, bundle);
862                 } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
863                     Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
864                     Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
865                     Callback.this.onPrepareFromUri(uri, bundle);
866                 } else {
867                     Callback.this.onCustomAction(action, extras);
868                 }
869             }
870         }
871 
872         private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
873 
874             @Override
onPlayFromUri(Uri uri, Bundle extras)875             public void onPlayFromUri(Uri uri, Bundle extras) {
876                 Callback.this.onPlayFromUri(uri, extras);
877             }
878         }
879 
880         private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
881 
882             @Override
onPrepare()883             public void onPrepare() {
884                 Callback.this.onPrepare();
885             }
886 
887             @Override
onPrepareFromMediaId(String mediaId, Bundle extras)888             public void onPrepareFromMediaId(String mediaId, Bundle extras) {
889                 Callback.this.onPrepareFromMediaId(mediaId, extras);
890             }
891 
892             @Override
onPrepareFromSearch(String query, Bundle extras)893             public void onPrepareFromSearch(String query, Bundle extras) {
894                 Callback.this.onPrepareFromSearch(query, extras);
895             }
896 
897             @Override
onPrepareFromUri(Uri uri, Bundle extras)898             public void onPrepareFromUri(Uri uri, Bundle extras) {
899                 Callback.this.onPrepareFromUri(uri, extras);
900             }
901         }
902     }
903 
904     /**
905      * Represents an ongoing session. This may be passed to apps by the session
906      * owner to allow them to create a {@link MediaControllerCompat} to communicate with
907      * the session.
908      */
909     public static final class Token implements Parcelable {
910         private final Object mInner;
911 
Token(Object inner)912         Token(Object inner) {
913             mInner = inner;
914         }
915 
916         /**
917          * Creates a compat Token from a framework
918          * {@link android.media.session.MediaSession.Token} object.
919          * <p>
920          * This method is only supported on
921          * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
922          * </p>
923          *
924          * @param token The framework token object.
925          * @return A compat Token for use with {@link MediaControllerCompat}.
926          */
fromToken(Object token)927         public static Token fromToken(Object token) {
928             if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
929                 return null;
930             }
931             return new Token(MediaSessionCompatApi21.verifyToken(token));
932         }
933 
934         @Override
describeContents()935         public int describeContents() {
936             return 0;
937         }
938 
939         @Override
writeToParcel(Parcel dest, int flags)940         public void writeToParcel(Parcel dest, int flags) {
941             if (android.os.Build.VERSION.SDK_INT >= 21) {
942                 dest.writeParcelable((Parcelable) mInner, flags);
943             } else {
944                 dest.writeStrongBinder((IBinder) mInner);
945             }
946         }
947 
948         @Override
hashCode()949         public int hashCode() {
950             if (mInner == null) {
951                 return 0;
952             }
953             return mInner.hashCode();
954         }
955 
956         @Override
equals(Object obj)957         public boolean equals(Object obj) {
958             if (this == obj) {
959                 return true;
960             }
961             if (!(obj instanceof Token)) {
962                 return false;
963             }
964 
965             Token other = (Token) obj;
966             if (mInner == null) {
967                 return other.mInner == null;
968             }
969             if (other.mInner == null) {
970                 return false;
971             }
972             return mInner.equals(other.mInner);
973         }
974 
975         /**
976          * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
977          * <p>
978          * This method is only supported on API 21+.
979          * </p>
980          *
981          * @return The underlying {@link android.media.session.MediaSession.Token} object,
982          * or null if none.
983          */
getToken()984         public Object getToken() {
985             return mInner;
986         }
987 
988         public static final Parcelable.Creator<Token> CREATOR
989                 = new Parcelable.Creator<Token>() {
990             @Override
991             public Token createFromParcel(Parcel in) {
992                 Object inner;
993                 if (android.os.Build.VERSION.SDK_INT >= 21) {
994                     inner = in.readParcelable(null);
995                 } else {
996                     inner = in.readStrongBinder();
997                 }
998                 return new Token(inner);
999             }
1000 
1001             @Override
1002             public Token[] newArray(int size) {
1003                 return new Token[size];
1004             }
1005         };
1006     }
1007 
1008     /**
1009      * A single item that is part of the play queue. It contains a description
1010      * of the item and its id in the queue.
1011      */
1012     public static final class QueueItem implements Parcelable {
1013         /**
1014          * This id is reserved. No items can be explicitly assigned this id.
1015          */
1016         public static final int UNKNOWN_ID = -1;
1017 
1018         private final MediaDescriptionCompat mDescription;
1019         private final long mId;
1020 
1021         private Object mItem;
1022 
1023         /**
1024          * Create a new {@link MediaSessionCompat.QueueItem}.
1025          *
1026          * @param description The {@link MediaDescriptionCompat} for this item.
1027          * @param id An identifier for this item. It must be unique within the
1028          *            play queue and cannot be {@link #UNKNOWN_ID}.
1029          */
QueueItem(MediaDescriptionCompat description, long id)1030         public QueueItem(MediaDescriptionCompat description, long id) {
1031             this(null, description, id);
1032         }
1033 
QueueItem(Object queueItem, MediaDescriptionCompat description, long id)1034         private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
1035             if (description == null) {
1036                 throw new IllegalArgumentException("Description cannot be null.");
1037             }
1038             if (id == UNKNOWN_ID) {
1039                 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1040             }
1041             mDescription = description;
1042             mId = id;
1043             mItem = queueItem;
1044         }
1045 
QueueItem(Parcel in)1046         private QueueItem(Parcel in) {
1047             mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
1048             mId = in.readLong();
1049         }
1050 
1051         /**
1052          * Get the description for this item.
1053          */
getDescription()1054         public MediaDescriptionCompat getDescription() {
1055             return mDescription;
1056         }
1057 
1058         /**
1059          * Get the queue id for this item.
1060          */
getQueueId()1061         public long getQueueId() {
1062             return mId;
1063         }
1064 
1065         @Override
writeToParcel(Parcel dest, int flags)1066         public void writeToParcel(Parcel dest, int flags) {
1067             mDescription.writeToParcel(dest, flags);
1068             dest.writeLong(mId);
1069         }
1070 
1071         @Override
describeContents()1072         public int describeContents() {
1073             return 0;
1074         }
1075 
1076         /**
1077          * Get the underlying
1078          * {@link android.media.session.MediaSession.QueueItem}.
1079          * <p>
1080          * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
1081          * is returned.
1082          *
1083          * @return The underlying
1084          *         {@link android.media.session.MediaSession.QueueItem} or null.
1085          */
getQueueItem()1086         public Object getQueueItem() {
1087             if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
1088                 return mItem;
1089             }
1090             mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
1091                     mId);
1092             return mItem;
1093         }
1094 
1095         /**
1096          * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1097          * object.
1098          * <p>
1099          * This method is only supported on API 21+. On API 20 and below, it returns null.
1100          * </p>
1101          *
1102          * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1103          * @return An equivalent {@link QueueItem} object, or null if none.
1104          * @deprecated Use {@link #fromQueueItem(Object)} instead.
1105          */
1106         @Deprecated
obtain(Object queueItem)1107         public static QueueItem obtain(Object queueItem) {
1108             return fromQueueItem(queueItem);
1109         }
1110 
1111         /**
1112          * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1113          * object.
1114          * <p>
1115          * This method is only supported on API 21+. On API 20 and below, it returns null.
1116          * </p>
1117          *
1118          * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1119          * @return An equivalent {@link QueueItem} object, or null if none.
1120          */
fromQueueItem(Object queueItem)1121         public static QueueItem fromQueueItem(Object queueItem) {
1122             if (queueItem == null || Build.VERSION.SDK_INT < 21) {
1123                 return null;
1124             }
1125             Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
1126             MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
1127                     descriptionObj);
1128             long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
1129             return new QueueItem(queueItem, description, id);
1130         }
1131 
1132         /**
1133          * Creates a list of {@link QueueItem} objects from a framework
1134          * {@link android.media.session.MediaSession.QueueItem} object list.
1135          * <p>
1136          * This method is only supported on API 21+. On API 20 and below, it returns null.
1137          * </p>
1138          *
1139          * @param itemList A list of {@link android.media.session.MediaSession.QueueItem} objects.
1140          * @return An equivalent list of {@link QueueItem} objects, or null if none.
1141          */
fromQueueItemList(List<?> itemList)1142         public static List<QueueItem> fromQueueItemList(List<?> itemList) {
1143             if (itemList == null || Build.VERSION.SDK_INT < 21) {
1144                 return null;
1145             }
1146             List<QueueItem> items = new ArrayList<>();
1147             for (Object itemObj : itemList) {
1148                 items.add(fromQueueItem(itemObj));
1149             }
1150             return items;
1151         }
1152 
1153         public static final Creator<MediaSessionCompat.QueueItem> CREATOR
1154                 = new Creator<MediaSessionCompat.QueueItem>() {
1155 
1156             @Override
1157             public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
1158                 return new MediaSessionCompat.QueueItem(p);
1159             }
1160 
1161             @Override
1162             public MediaSessionCompat.QueueItem[] newArray(int size) {
1163                 return new MediaSessionCompat.QueueItem[size];
1164             }
1165         };
1166 
1167         @Override
toString()1168         public String toString() {
1169             return "MediaSession.QueueItem {" +
1170                     "Description=" + mDescription +
1171                     ", Id=" + mId + " }";
1172         }
1173     }
1174 
1175     /**
1176      * This is a wrapper for {@link ResultReceiver} for sending over aidl
1177      * interfaces. The framework version was not exposed to aidls until
1178      * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
1179      */
1180     static final class ResultReceiverWrapper implements Parcelable {
1181         private ResultReceiver mResultReceiver;
1182 
ResultReceiverWrapper(ResultReceiver resultReceiver)1183         public ResultReceiverWrapper(ResultReceiver resultReceiver) {
1184             mResultReceiver = resultReceiver;
1185         }
1186 
ResultReceiverWrapper(Parcel in)1187         ResultReceiverWrapper(Parcel in) {
1188             mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
1189         }
1190 
1191         public static final Creator<ResultReceiverWrapper>
1192                 CREATOR = new Creator<ResultReceiverWrapper>() {
1193             @Override
1194             public ResultReceiverWrapper createFromParcel(Parcel p) {
1195                 return new ResultReceiverWrapper(p);
1196             }
1197 
1198             @Override
1199             public ResultReceiverWrapper[] newArray(int size) {
1200                 return new ResultReceiverWrapper[size];
1201             }
1202         };
1203 
1204         @Override
describeContents()1205         public int describeContents() {
1206             return 0;
1207         }
1208 
1209         @Override
writeToParcel(Parcel dest, int flags)1210         public void writeToParcel(Parcel dest, int flags) {
1211             mResultReceiver.writeToParcel(dest, flags);
1212         }
1213     }
1214 
1215     public interface OnActiveChangeListener {
onActiveChanged()1216         void onActiveChanged();
1217     }
1218 
1219     interface MediaSessionImpl {
setCallback(Callback callback, Handler handler)1220         void setCallback(Callback callback, Handler handler);
setFlags(@essionFlags int flags)1221         void setFlags(@SessionFlags int flags);
setPlaybackToLocal(int stream)1222         void setPlaybackToLocal(int stream);
setPlaybackToRemote(VolumeProviderCompat volumeProvider)1223         void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
setActive(boolean active)1224         void setActive(boolean active);
isActive()1225         boolean isActive();
sendSessionEvent(String event, Bundle extras)1226         void sendSessionEvent(String event, Bundle extras);
release()1227         void release();
getSessionToken()1228         Token getSessionToken();
setPlaybackState(PlaybackStateCompat state)1229         void setPlaybackState(PlaybackStateCompat state);
setMetadata(MediaMetadataCompat metadata)1230         void setMetadata(MediaMetadataCompat metadata);
1231 
setSessionActivity(PendingIntent pi)1232         void setSessionActivity(PendingIntent pi);
1233 
setMediaButtonReceiver(PendingIntent mbr)1234         void setMediaButtonReceiver(PendingIntent mbr);
setQueue(List<QueueItem> queue)1235         void setQueue(List<QueueItem> queue);
setQueueTitle(CharSequence title)1236         void setQueueTitle(CharSequence title);
1237 
setRatingType(@atingCompat.Style int type)1238         void setRatingType(@RatingCompat.Style int type);
setExtras(Bundle extras)1239         void setExtras(Bundle extras);
1240 
getMediaSession()1241         Object getMediaSession();
1242 
getRemoteControlClient()1243         Object getRemoteControlClient();
1244 
getCallingPackage()1245         String getCallingPackage();
1246     }
1247 
1248     static class MediaSessionImplBase implements MediaSessionImpl {
1249         private final Context mContext;
1250         private final ComponentName mMediaButtonReceiverComponentName;
1251         private final PendingIntent mMediaButtonReceiverIntent;
1252         private final Object mRccObj;
1253         private final MediaSessionStub mStub;
1254         private final Token mToken;
1255         private final String mPackageName;
1256         private final String mTag;
1257         private final AudioManager mAudioManager;
1258 
1259         private final Object mLock = new Object();
1260         private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
1261                 = new RemoteCallbackList<>();
1262 
1263         private MessageHandler mHandler;
1264         private boolean mDestroyed = false;
1265         private boolean mIsActive = false;
1266         private boolean mIsRccRegistered = false;
1267         private boolean mIsMbrRegistered = false;
1268         private volatile Callback mCallback;
1269 
1270         private @SessionFlags int mFlags;
1271 
1272         private MediaMetadataCompat mMetadata;
1273         private PlaybackStateCompat mState;
1274         private PendingIntent mSessionActivity;
1275         private List<QueueItem> mQueue;
1276         private CharSequence mQueueTitle;
1277         private @RatingCompat.Style int mRatingType;
1278         private Bundle mExtras;
1279 
1280         private int mVolumeType;
1281         private int mLocalStream;
1282         private VolumeProviderCompat mVolumeProvider;
1283 
1284         private VolumeProviderCompat.Callback mVolumeCallback
1285                 = new VolumeProviderCompat.Callback() {
1286             @Override
1287             public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
1288                 if (mVolumeProvider != volumeProvider) {
1289                     return;
1290                 }
1291                 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1292                         volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
1293                         volumeProvider.getCurrentVolume());
1294                 sendVolumeInfoChanged(info);
1295             }
1296         };
1297 
MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent, PendingIntent mbrIntent)1298         public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
1299                 PendingIntent mbrIntent) {
1300             if (mbrComponent == null) {
1301                 mbrComponent = MediaButtonReceiver.getMediaButtonReceiverComponent(context);
1302                 if (mbrComponent == null) {
1303                     Log.w(TAG, "Couldn't find a unique registered media button receiver in the "
1304                             + "given context.");
1305                 }
1306             }
1307             if (mbrComponent != null && mbrIntent == null) {
1308                 // construct a PendingIntent for the media button
1309                 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1310                 // the associated intent will be handled by the component being registered
1311                 mediaButtonIntent.setComponent(mbrComponent);
1312                 mbrIntent = PendingIntent.getBroadcast(context,
1313                         0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
1314             }
1315             if (mbrComponent == null) {
1316                 throw new IllegalArgumentException(
1317                         "MediaButtonReceiver component may not be null.");
1318             }
1319             mContext = context;
1320             mPackageName = context.getPackageName();
1321             mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1322             mTag = tag;
1323             mMediaButtonReceiverComponentName = mbrComponent;
1324             mMediaButtonReceiverIntent = mbrIntent;
1325             mStub = new MediaSessionStub();
1326             mToken = new Token(mStub);
1327 
1328             mRatingType = RatingCompat.RATING_NONE;
1329             mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1330             mLocalStream = AudioManager.STREAM_MUSIC;
1331             if (android.os.Build.VERSION.SDK_INT >= 14) {
1332                 mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent);
1333             } else {
1334                 mRccObj = null;
1335             }
1336         }
1337 
1338         @Override
setCallback(Callback callback, Handler handler)1339         public void setCallback(Callback callback, Handler handler) {
1340             mCallback = callback;
1341             if (callback == null) {
1342                 // There's nothing to unregister on API < 18 since media buttons
1343                 // all go through the media button receiver
1344                 if (android.os.Build.VERSION.SDK_INT >= 18) {
1345                     MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
1346                 }
1347                 if (android.os.Build.VERSION.SDK_INT >= 19) {
1348                     MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
1349                 }
1350             } else {
1351                 if (handler == null) {
1352                     handler = new Handler();
1353                 }
1354                 synchronized (mLock) {
1355                     mHandler = new MessageHandler(handler.getLooper());
1356                 }
1357                 MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() {
1358                     @Override
1359                     public void onSetRating(Object ratingObj) {
1360                         postToHandler(MessageHandler.MSG_RATE,
1361                                 RatingCompat.fromRating(ratingObj));
1362                     }
1363 
1364                     @Override
1365                     public void onSeekTo(long pos) {
1366                         postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1367                     }
1368                 };
1369                 if (android.os.Build.VERSION.SDK_INT >= 18) {
1370                     Object onPositionUpdateObj = MediaSessionCompatApi18
1371                             .createPlaybackPositionUpdateListener(cb19);
1372                     MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
1373                             onPositionUpdateObj);
1374                 }
1375                 if (android.os.Build.VERSION.SDK_INT >= 19) {
1376                     Object onMetadataUpdateObj = MediaSessionCompatApi19
1377                             .createMetadataUpdateListener(cb19);
1378                     MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
1379                             onMetadataUpdateObj);
1380                 }
1381             }
1382         }
1383 
postToHandler(int what)1384         private void postToHandler(int what) {
1385             postToHandler(what, null);
1386         }
1387 
postToHandler(int what, Object obj)1388         private void postToHandler(int what, Object obj) {
1389             postToHandler(what, obj, null);
1390         }
1391 
postToHandler(int what, Object obj, Bundle extras)1392         private void postToHandler(int what, Object obj, Bundle extras) {
1393             synchronized (mLock) {
1394                 if (mHandler != null) {
1395                     mHandler.post(what, obj, extras);
1396                 }
1397             }
1398         }
1399 
1400         @Override
setFlags(@essionFlags int flags)1401         public void setFlags(@SessionFlags int flags) {
1402             synchronized (mLock) {
1403                 mFlags = flags;
1404             }
1405             update();
1406         }
1407 
1408         @Override
setPlaybackToLocal(int stream)1409         public void setPlaybackToLocal(int stream) {
1410             if (mVolumeProvider != null) {
1411                 mVolumeProvider.setCallback(null);
1412             }
1413             mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1414             ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1415                     VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1416                     mAudioManager.getStreamMaxVolume(mLocalStream),
1417                     mAudioManager.getStreamVolume(mLocalStream));
1418             sendVolumeInfoChanged(info);
1419         }
1420 
1421         @Override
setPlaybackToRemote(VolumeProviderCompat volumeProvider)1422         public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1423             if (volumeProvider == null) {
1424                 throw new IllegalArgumentException("volumeProvider may not be null");
1425             }
1426             if (mVolumeProvider != null) {
1427                 mVolumeProvider.setCallback(null);
1428             }
1429             mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1430             mVolumeProvider = volumeProvider;
1431             ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1432                     mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1433                     mVolumeProvider.getCurrentVolume());
1434             sendVolumeInfoChanged(info);
1435 
1436             volumeProvider.setCallback(mVolumeCallback);
1437         }
1438 
1439         @Override
setActive(boolean active)1440         public void setActive(boolean active) {
1441             if (active == mIsActive) {
1442                 return;
1443             }
1444             mIsActive = active;
1445             if (update()) {
1446                 setMetadata(mMetadata);
1447                 setPlaybackState(mState);
1448             }
1449         }
1450 
1451         @Override
isActive()1452         public boolean isActive() {
1453             return mIsActive;
1454         }
1455 
1456         @Override
sendSessionEvent(String event, Bundle extras)1457         public void sendSessionEvent(String event, Bundle extras) {
1458             sendEvent(event, extras);
1459         }
1460 
1461         @Override
release()1462         public void release() {
1463             mIsActive = false;
1464             mDestroyed = true;
1465             update();
1466             sendSessionDestroyed();
1467         }
1468 
1469         @Override
getSessionToken()1470         public Token getSessionToken() {
1471             return mToken;
1472         }
1473 
1474         @Override
setPlaybackState(PlaybackStateCompat state)1475         public void setPlaybackState(PlaybackStateCompat state) {
1476             synchronized (mLock) {
1477                 mState = state;
1478             }
1479             sendState(state);
1480             if (!mIsActive) {
1481                 // Don't set the state until after the RCC is registered
1482                 return;
1483             }
1484             if (state == null) {
1485                 if (android.os.Build.VERSION.SDK_INT >= 14) {
1486                     MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1487                     MediaSessionCompatApi14.setTransportControlFlags(mRccObj, 0);
1488                 }
1489             } else {
1490                 // Set state
1491                 if (android.os.Build.VERSION.SDK_INT >= 18) {
1492                     MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
1493                             state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
1494                 } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1495                     MediaSessionCompatApi14.setState(mRccObj, state.getState());
1496                 }
1497 
1498                 // Set transport control flags
1499                 if (android.os.Build.VERSION.SDK_INT >= 19) {
1500                     MediaSessionCompatApi19.setTransportControlFlags(mRccObj, state.getActions());
1501                 } else if (android.os.Build.VERSION.SDK_INT >= 18) {
1502                     MediaSessionCompatApi18.setTransportControlFlags(mRccObj, state.getActions());
1503                 } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1504                     MediaSessionCompatApi14.setTransportControlFlags(mRccObj, state.getActions());
1505                 }
1506             }
1507         }
1508 
1509         @Override
setMetadata(MediaMetadataCompat metadata)1510         public void setMetadata(MediaMetadataCompat metadata) {
1511             if (metadata != null) {
1512                 // Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the
1513                 // metadata if necessary. Bitmaps can be scaled down if they are large.
1514                 metadata = new MediaMetadataCompat.Builder(metadata, sMaxBitmapSize).build();
1515             }
1516 
1517             synchronized (mLock) {
1518                 mMetadata = metadata;
1519             }
1520             sendMetadata(metadata);
1521             if (!mIsActive) {
1522                 // Don't set metadata until after the rcc has been registered
1523                 return;
1524             }
1525             if (android.os.Build.VERSION.SDK_INT >= 19) {
1526                 MediaSessionCompatApi19.setMetadata(mRccObj,
1527                         metadata == null ? null : metadata.getBundle(),
1528                         mState == null ? 0 : mState.getActions());
1529             } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1530                 MediaSessionCompatApi14.setMetadata(mRccObj,
1531                         metadata == null ? null : metadata.getBundle());
1532             }
1533         }
1534 
1535         @Override
setSessionActivity(PendingIntent pi)1536         public void setSessionActivity(PendingIntent pi) {
1537             synchronized (mLock) {
1538                 mSessionActivity = pi;
1539             }
1540         }
1541 
1542         @Override
setMediaButtonReceiver(PendingIntent mbr)1543         public void setMediaButtonReceiver(PendingIntent mbr) {
1544             // Do nothing, changing this is not supported before API 21.
1545         }
1546 
1547         @Override
setQueue(List<QueueItem> queue)1548         public void setQueue(List<QueueItem> queue) {
1549             mQueue = queue;
1550             sendQueue(queue);
1551         }
1552 
1553         @Override
setQueueTitle(CharSequence title)1554         public void setQueueTitle(CharSequence title) {
1555             mQueueTitle = title;
1556             sendQueueTitle(title);
1557         }
1558 
1559         @Override
getMediaSession()1560         public Object getMediaSession() {
1561             return null;
1562         }
1563 
1564         @Override
getRemoteControlClient()1565         public Object getRemoteControlClient() {
1566             return mRccObj;
1567         }
1568 
1569         @Override
getCallingPackage()1570         public String getCallingPackage() {
1571             return null;
1572         }
1573 
1574         @Override
setRatingType(@atingCompat.Style int type)1575         public void setRatingType(@RatingCompat.Style int type) {
1576             mRatingType = type;
1577         }
1578 
1579         @Override
setExtras(Bundle extras)1580         public void setExtras(Bundle extras) {
1581             mExtras = extras;
1582             sendExtras(extras);
1583         }
1584 
1585         // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
update()1586         private boolean update() {
1587             boolean registeredRcc = false;
1588             if (mIsActive) {
1589                 // Register a MBR if it's supported, unregister it
1590                 // if support was removed.
1591                 if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
1592                     if (android.os.Build.VERSION.SDK_INT >= 18) {
1593                         MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
1594                                 mMediaButtonReceiverIntent,
1595                                 mMediaButtonReceiverComponentName);
1596                     } else {
1597                         AudioManager am = (AudioManager) mContext.getSystemService(
1598                                 Context.AUDIO_SERVICE);
1599                         am.registerMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1600                     }
1601                     mIsMbrRegistered = true;
1602                 } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
1603                     if (android.os.Build.VERSION.SDK_INT >= 18) {
1604                         MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1605                                 mMediaButtonReceiverIntent,
1606                                 mMediaButtonReceiverComponentName);
1607                     } else {
1608                         AudioManager am = (AudioManager) mContext.getSystemService(
1609                                 Context.AUDIO_SERVICE);
1610                         am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1611                     }
1612                     mIsMbrRegistered = false;
1613                 }
1614                 // On API 14+ register a RCC if it's supported, unregister it if
1615                 // not.
1616                 if (android.os.Build.VERSION.SDK_INT >= 14) {
1617                     if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
1618                         MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
1619                         mIsRccRegistered = true;
1620                         registeredRcc = true;
1621                     } else if (mIsRccRegistered
1622                             && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
1623                         // RCC keeps the state while the system resets its state internally when
1624                         // we register RCC. Reset the state so that the states in RCC and the system
1625                         // are in sync when we re-register the RCC.
1626                         MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1627                         MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1628                         mIsRccRegistered = false;
1629                     }
1630                 }
1631             } else {
1632                 // When inactive remove any registered components.
1633                 if (mIsMbrRegistered) {
1634                     if (android.os.Build.VERSION.SDK_INT >= 18) {
1635                         MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1636                                 mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName);
1637                     } else {
1638                         AudioManager am = (AudioManager) mContext.getSystemService(
1639                                 Context.AUDIO_SERVICE);
1640                         am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1641                     }
1642                     mIsMbrRegistered = false;
1643                 }
1644                 if (mIsRccRegistered) {
1645                     // RCC keeps the state while the system resets its state internally when
1646                     // we register RCC. Reset the state so that the states in RCC and the system
1647                     // are in sync when we re-register the RCC.
1648                     MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1649                     MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1650                     mIsRccRegistered = false;
1651                 }
1652             }
1653             return registeredRcc;
1654         }
1655 
adjustVolume(int direction, int flags)1656         private void adjustVolume(int direction, int flags) {
1657             if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1658                 if (mVolumeProvider != null) {
1659                     mVolumeProvider.onAdjustVolume(direction);
1660                 }
1661             } else {
1662                 mAudioManager.adjustStreamVolume(mLocalStream, direction, flags);
1663             }
1664         }
1665 
setVolumeTo(int value, int flags)1666         private void setVolumeTo(int value, int flags) {
1667             if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1668                 if (mVolumeProvider != null) {
1669                     mVolumeProvider.onSetVolumeTo(value);
1670                 }
1671             } else {
1672                 mAudioManager.setStreamVolume(mLocalStream, value, flags);
1673             }
1674         }
1675 
getStateWithUpdatedPosition()1676         private PlaybackStateCompat getStateWithUpdatedPosition() {
1677             PlaybackStateCompat state;
1678             long duration = -1;
1679             synchronized (mLock) {
1680                 state = mState;
1681                 if (mMetadata != null
1682                         && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
1683                     duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
1684                 }
1685             }
1686 
1687             PlaybackStateCompat result = null;
1688             if (state != null) {
1689                 if (state.getState() == PlaybackStateCompat.STATE_PLAYING
1690                         || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
1691                         || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
1692                     long updateTime = state.getLastPositionUpdateTime();
1693                     long currentTime = SystemClock.elapsedRealtime();
1694                     if (updateTime > 0) {
1695                         long position = (long) (state.getPlaybackSpeed()
1696                                 * (currentTime - updateTime)) + state.getPosition();
1697                         if (duration >= 0 && position > duration) {
1698                             position = duration;
1699                         } else if (position < 0) {
1700                             position = 0;
1701                         }
1702                         PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
1703                                 state);
1704                         builder.setState(state.getState(), position, state.getPlaybackSpeed(),
1705                                 currentTime);
1706                         result = builder.build();
1707                     }
1708                 }
1709             }
1710             return result == null ? state : result;
1711         }
1712 
sendVolumeInfoChanged(ParcelableVolumeInfo info)1713         private void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
1714             int size = mControllerCallbacks.beginBroadcast();
1715             for (int i = size - 1; i >= 0; i--) {
1716                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1717                 try {
1718                     cb.onVolumeInfoChanged(info);
1719                 } catch (RemoteException e) {
1720                 }
1721             }
1722             mControllerCallbacks.finishBroadcast();
1723         }
1724 
sendSessionDestroyed()1725         private void sendSessionDestroyed() {
1726             int size = mControllerCallbacks.beginBroadcast();
1727             for (int i = size - 1; i >= 0; i--) {
1728                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1729                 try {
1730                     cb.onSessionDestroyed();
1731                 } catch (RemoteException e) {
1732                 }
1733             }
1734             mControllerCallbacks.finishBroadcast();
1735             mControllerCallbacks.kill();
1736         }
1737 
sendEvent(String event, Bundle extras)1738         private void sendEvent(String event, Bundle extras) {
1739             int size = mControllerCallbacks.beginBroadcast();
1740             for (int i = size - 1; i >= 0; i--) {
1741                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1742                 try {
1743                     cb.onEvent(event, extras);
1744                 } catch (RemoteException e) {
1745                 }
1746             }
1747             mControllerCallbacks.finishBroadcast();
1748         }
1749 
sendState(PlaybackStateCompat state)1750         private void sendState(PlaybackStateCompat state) {
1751             int size = mControllerCallbacks.beginBroadcast();
1752             for (int i = size - 1; i >= 0; i--) {
1753                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1754                 try {
1755                     cb.onPlaybackStateChanged(state);
1756                 } catch (RemoteException e) {
1757                 }
1758             }
1759             mControllerCallbacks.finishBroadcast();
1760         }
1761 
sendMetadata(MediaMetadataCompat metadata)1762         private void sendMetadata(MediaMetadataCompat metadata) {
1763             int size = mControllerCallbacks.beginBroadcast();
1764             for (int i = size - 1; i >= 0; i--) {
1765                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1766                 try {
1767                     cb.onMetadataChanged(metadata);
1768                 } catch (RemoteException e) {
1769                 }
1770             }
1771             mControllerCallbacks.finishBroadcast();
1772         }
1773 
sendQueue(List<QueueItem> queue)1774         private void sendQueue(List<QueueItem> queue) {
1775             int size = mControllerCallbacks.beginBroadcast();
1776             for (int i = size - 1; i >= 0; i--) {
1777                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1778                 try {
1779                     cb.onQueueChanged(queue);
1780                 } catch (RemoteException e) {
1781                 }
1782             }
1783             mControllerCallbacks.finishBroadcast();
1784         }
1785 
sendQueueTitle(CharSequence queueTitle)1786         private void sendQueueTitle(CharSequence queueTitle) {
1787             int size = mControllerCallbacks.beginBroadcast();
1788             for (int i = size - 1; i >= 0; i--) {
1789                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1790                 try {
1791                     cb.onQueueTitleChanged(queueTitle);
1792                 } catch (RemoteException e) {
1793                 }
1794             }
1795             mControllerCallbacks.finishBroadcast();
1796         }
1797 
sendExtras(Bundle extras)1798         private void sendExtras(Bundle extras) {
1799             int size = mControllerCallbacks.beginBroadcast();
1800             for (int i = size - 1; i >= 0; i--) {
1801                 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1802                 try {
1803                     cb.onExtrasChanged(extras);
1804                 } catch (RemoteException e) {
1805                 }
1806             }
1807             mControllerCallbacks.finishBroadcast();
1808         }
1809 
1810         class MediaSessionStub extends IMediaSession.Stub {
1811             @Override
sendCommand(String command, Bundle args, ResultReceiverWrapper cb)1812             public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
1813                 postToHandler(MessageHandler.MSG_COMMAND,
1814                         new Command(command, args, cb.mResultReceiver));
1815             }
1816 
1817             @Override
sendMediaButton(KeyEvent mediaButton)1818             public boolean sendMediaButton(KeyEvent mediaButton) {
1819                 boolean handlesMediaButtons =
1820                         (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
1821                 if (handlesMediaButtons) {
1822                     postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
1823                 }
1824                 return handlesMediaButtons;
1825             }
1826 
1827             @Override
registerCallbackListener(IMediaControllerCallback cb)1828             public void registerCallbackListener(IMediaControllerCallback cb) {
1829                 // If this session is already destroyed tell the caller and
1830                 // don't add them.
1831                 if (mDestroyed) {
1832                     try {
1833                         cb.onSessionDestroyed();
1834                     } catch (Exception e) {
1835                         // ignored
1836                     }
1837                     return;
1838                 }
1839                 mControllerCallbacks.register(cb);
1840             }
1841 
1842             @Override
unregisterCallbackListener(IMediaControllerCallback cb)1843             public void unregisterCallbackListener(IMediaControllerCallback cb) {
1844                 mControllerCallbacks.unregister(cb);
1845             }
1846 
1847             @Override
getPackageName()1848             public String getPackageName() {
1849                 // mPackageName is final so doesn't need synchronize block
1850                 return mPackageName;
1851             }
1852 
1853             @Override
getTag()1854             public String getTag() {
1855                 // mTag is final so doesn't need synchronize block
1856                 return mTag;
1857             }
1858 
1859             @Override
getLaunchPendingIntent()1860             public PendingIntent getLaunchPendingIntent() {
1861                 synchronized (mLock) {
1862                     return mSessionActivity;
1863                 }
1864             }
1865 
1866             @Override
1867             @SessionFlags
getFlags()1868             public long getFlags() {
1869                 synchronized (mLock) {
1870                     return mFlags;
1871                 }
1872             }
1873 
1874             @Override
getVolumeAttributes()1875             public ParcelableVolumeInfo getVolumeAttributes() {
1876                 int controlType;
1877                 int max;
1878                 int current;
1879                 int stream;
1880                 int volumeType;
1881                 synchronized (mLock) {
1882                     volumeType = mVolumeType;
1883                     stream = mLocalStream;
1884                     VolumeProviderCompat vp = mVolumeProvider;
1885                     if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1886                         controlType = vp.getVolumeControl();
1887                         max = vp.getMaxVolume();
1888                         current = vp.getCurrentVolume();
1889                     } else {
1890                         controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
1891                         max = mAudioManager.getStreamMaxVolume(stream);
1892                         current = mAudioManager.getStreamVolume(stream);
1893                     }
1894                 }
1895                 return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
1896             }
1897 
1898             @Override
adjustVolume(int direction, int flags, String packageName)1899             public void adjustVolume(int direction, int flags, String packageName) {
1900                 MediaSessionImplBase.this.adjustVolume(direction, flags);
1901             }
1902 
1903             @Override
setVolumeTo(int value, int flags, String packageName)1904             public void setVolumeTo(int value, int flags, String packageName) {
1905                 MediaSessionImplBase.this.setVolumeTo(value, flags);
1906             }
1907 
1908             @Override
prepare()1909             public void prepare() throws RemoteException {
1910                 postToHandler(MessageHandler.MSG_PREPARE);
1911             }
1912 
1913             @Override
prepareFromMediaId(String mediaId, Bundle extras)1914             public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1915                 postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
1916             }
1917 
1918             @Override
prepareFromSearch(String query, Bundle extras)1919             public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
1920                 postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
1921             }
1922 
1923             @Override
prepareFromUri(Uri uri, Bundle extras)1924             public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
1925                 postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
1926             }
1927 
1928             @Override
play()1929             public void play() throws RemoteException {
1930                 postToHandler(MessageHandler.MSG_PLAY);
1931             }
1932 
1933             @Override
playFromMediaId(String mediaId, Bundle extras)1934             public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1935                 postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
1936             }
1937 
1938             @Override
playFromSearch(String query, Bundle extras)1939             public void playFromSearch(String query, Bundle extras) throws RemoteException {
1940                 postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
1941             }
1942 
1943             @Override
playFromUri(Uri uri, Bundle extras)1944             public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
1945                 postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
1946             }
1947 
1948             @Override
skipToQueueItem(long id)1949             public void skipToQueueItem(long id) {
1950                 postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
1951             }
1952 
1953             @Override
pause()1954             public void pause() throws RemoteException {
1955                 postToHandler(MessageHandler.MSG_PAUSE);
1956             }
1957 
1958             @Override
stop()1959             public void stop() throws RemoteException {
1960                 postToHandler(MessageHandler.MSG_STOP);
1961             }
1962 
1963             @Override
next()1964             public void next() throws RemoteException {
1965                 postToHandler(MessageHandler.MSG_NEXT);
1966             }
1967 
1968             @Override
previous()1969             public void previous() throws RemoteException {
1970                 postToHandler(MessageHandler.MSG_PREVIOUS);
1971             }
1972 
1973             @Override
fastForward()1974             public void fastForward() throws RemoteException {
1975                 postToHandler(MessageHandler.MSG_FAST_FORWARD);
1976             }
1977 
1978             @Override
rewind()1979             public void rewind() throws RemoteException {
1980                 postToHandler(MessageHandler.MSG_REWIND);
1981             }
1982 
1983             @Override
seekTo(long pos)1984             public void seekTo(long pos) throws RemoteException {
1985                 postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1986             }
1987 
1988             @Override
rate(RatingCompat rating)1989             public void rate(RatingCompat rating) throws RemoteException {
1990                 postToHandler(MessageHandler.MSG_RATE, rating);
1991             }
1992 
1993             @Override
sendCustomAction(String action, Bundle args)1994             public void sendCustomAction(String action, Bundle args)
1995                     throws RemoteException {
1996                 postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
1997             }
1998 
1999             @Override
getMetadata()2000             public MediaMetadataCompat getMetadata() {
2001                 return mMetadata;
2002             }
2003 
2004             @Override
getPlaybackState()2005             public PlaybackStateCompat getPlaybackState() {
2006                 return getStateWithUpdatedPosition();
2007             }
2008 
2009             @Override
getQueue()2010             public List<QueueItem> getQueue() {
2011                 synchronized (mLock) {
2012                     return mQueue;
2013                 }
2014             }
2015 
2016             @Override
getQueueTitle()2017             public CharSequence getQueueTitle() {
2018                 return mQueueTitle;
2019             }
2020 
2021             @Override
getExtras()2022             public Bundle getExtras() {
2023                 synchronized (mLock) {
2024                     return mExtras;
2025                 }
2026             }
2027 
2028             @Override
2029             @RatingCompat.Style
getRatingType()2030             public int getRatingType() {
2031                 return mRatingType;
2032             }
2033 
2034             @Override
isTransportControlEnabled()2035             public boolean isTransportControlEnabled() {
2036                 return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
2037             }
2038         }
2039 
2040         private static final class Command {
2041             public final String command;
2042             public final Bundle extras;
2043             public final ResultReceiver stub;
2044 
Command(String command, Bundle extras, ResultReceiver stub)2045             public Command(String command, Bundle extras, ResultReceiver stub) {
2046                 this.command = command;
2047                 this.extras = extras;
2048                 this.stub = stub;
2049             }
2050         }
2051 
2052         private class MessageHandler extends Handler {
2053 
2054             private static final int MSG_COMMAND = 1;
2055             private static final int MSG_ADJUST_VOLUME = 2;
2056             private static final int MSG_PREPARE = 3;
2057             private static final int MSG_PREPARE_MEDIA_ID = 4;
2058             private static final int MSG_PREPARE_SEARCH = 5;
2059             private static final int MSG_PREPARE_URI = 6;
2060             private static final int MSG_PLAY = 7;
2061             private static final int MSG_PLAY_MEDIA_ID = 8;
2062             private static final int MSG_PLAY_SEARCH = 9;
2063             private static final int MSG_PLAY_URI = 10;
2064             private static final int MSG_SKIP_TO_ITEM = 11;
2065             private static final int MSG_PAUSE = 12;
2066             private static final int MSG_STOP = 13;
2067             private static final int MSG_NEXT = 14;
2068             private static final int MSG_PREVIOUS = 15;
2069             private static final int MSG_FAST_FORWARD = 16;
2070             private static final int MSG_REWIND = 17;
2071             private static final int MSG_SEEK_TO = 18;
2072             private static final int MSG_RATE = 19;
2073             private static final int MSG_CUSTOM_ACTION = 20;
2074             private static final int MSG_MEDIA_BUTTON = 21;
2075             private static final int MSG_SET_VOLUME = 22;
2076 
2077             // KeyEvent constants only available on API 11+
2078             private static final int KEYCODE_MEDIA_PAUSE = 127;
2079             private static final int KEYCODE_MEDIA_PLAY = 126;
2080 
MessageHandler(Looper looper)2081             public MessageHandler(Looper looper) {
2082                 super(looper);
2083             }
2084 
post(int what, Object obj, Bundle bundle)2085             public void post(int what, Object obj, Bundle bundle) {
2086                 Message msg = obtainMessage(what, obj);
2087                 msg.setData(bundle);
2088                 msg.sendToTarget();
2089             }
2090 
post(int what, Object obj)2091             public void post(int what, Object obj) {
2092                 obtainMessage(what, obj).sendToTarget();
2093             }
2094 
post(int what)2095             public void post(int what) {
2096                 post(what, null);
2097             }
2098 
post(int what, Object obj, int arg1)2099             public void post(int what, Object obj, int arg1) {
2100                 obtainMessage(what, arg1, 0, obj).sendToTarget();
2101             }
2102 
2103             @Override
handleMessage(Message msg)2104             public void handleMessage(Message msg) {
2105                 MediaSessionCompat.Callback cb = mCallback;
2106                 if (cb == null) {
2107                     return;
2108                 }
2109                 switch (msg.what) {
2110                     case MSG_COMMAND:
2111                         Command cmd = (Command) msg.obj;
2112                         cb.onCommand(cmd.command, cmd.extras, cmd.stub);
2113                         break;
2114                     case MSG_MEDIA_BUTTON:
2115                         KeyEvent keyEvent = (KeyEvent) msg.obj;
2116                         Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
2117                         intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
2118                         // Let the Callback handle events first before using the default behavior
2119                         if (!cb.onMediaButtonEvent(intent)) {
2120                             onMediaButtonEvent(keyEvent, cb);
2121                         }
2122                         break;
2123                     case MSG_PREPARE:
2124                         cb.onPrepare();
2125                         break;
2126                     case MSG_PREPARE_MEDIA_ID:
2127                         cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
2128                         break;
2129                     case MSG_PREPARE_SEARCH:
2130                         cb.onPrepareFromSearch((String) msg.obj, msg.getData());
2131                         break;
2132                     case MSG_PREPARE_URI:
2133                         cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
2134                         break;
2135                     case MSG_PLAY:
2136                         cb.onPlay();
2137                         break;
2138                     case MSG_PLAY_MEDIA_ID:
2139                         cb.onPlayFromMediaId((String) msg.obj, msg.getData());
2140                         break;
2141                     case MSG_PLAY_SEARCH:
2142                         cb.onPlayFromSearch((String) msg.obj, msg.getData());
2143                         break;
2144                     case MSG_PLAY_URI:
2145                         cb.onPlayFromUri((Uri) msg.obj, msg.getData());
2146                         break;
2147                     case MSG_SKIP_TO_ITEM:
2148                         cb.onSkipToQueueItem((Long) msg.obj);
2149                         break;
2150                     case MSG_PAUSE:
2151                         cb.onPause();
2152                         break;
2153                     case MSG_STOP:
2154                         cb.onStop();
2155                         break;
2156                     case MSG_NEXT:
2157                         cb.onSkipToNext();
2158                         break;
2159                     case MSG_PREVIOUS:
2160                         cb.onSkipToPrevious();
2161                         break;
2162                     case MSG_FAST_FORWARD:
2163                         cb.onFastForward();
2164                         break;
2165                     case MSG_REWIND:
2166                         cb.onRewind();
2167                         break;
2168                     case MSG_SEEK_TO:
2169                         cb.onSeekTo((Long) msg.obj);
2170                         break;
2171                     case MSG_RATE:
2172                         cb.onSetRating((RatingCompat) msg.obj);
2173                         break;
2174                     case MSG_CUSTOM_ACTION:
2175                         cb.onCustomAction((String) msg.obj, msg.getData());
2176                         break;
2177                     case MSG_ADJUST_VOLUME:
2178                         adjustVolume((int) msg.obj, 0);
2179                         break;
2180                     case MSG_SET_VOLUME:
2181                         setVolumeTo((int) msg.obj, 0);
2182                         break;
2183                 }
2184             }
2185 
onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb)2186             private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
2187                 if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
2188                     return;
2189                 }
2190                 long validActions = mState == null ? 0 : mState.getActions();
2191                 switch (ke.getKeyCode()) {
2192                     // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
2193                     case KEYCODE_MEDIA_PLAY:
2194                         if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
2195                             cb.onPlay();
2196                         }
2197                         break;
2198                     // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
2199                     case KEYCODE_MEDIA_PAUSE:
2200                         if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
2201                             cb.onPause();
2202                         }
2203                         break;
2204                     case KeyEvent.KEYCODE_MEDIA_NEXT:
2205                         if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
2206                             cb.onSkipToNext();
2207                         }
2208                         break;
2209                     case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2210                         if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
2211                             cb.onSkipToPrevious();
2212                         }
2213                         break;
2214                     case KeyEvent.KEYCODE_MEDIA_STOP:
2215                         if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
2216                             cb.onStop();
2217                         }
2218                         break;
2219                     case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
2220                         if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
2221                             cb.onFastForward();
2222                         }
2223                         break;
2224                     case KeyEvent.KEYCODE_MEDIA_REWIND:
2225                         if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
2226                             cb.onRewind();
2227                         }
2228                         break;
2229                     case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2230                     case KeyEvent.KEYCODE_HEADSETHOOK:
2231                         boolean isPlaying = mState != null
2232                                 && mState.getState() == PlaybackStateCompat.STATE_PLAYING;
2233                         boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2234                                 | PlaybackStateCompat.ACTION_PLAY)) != 0;
2235                         boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2236                                 | PlaybackStateCompat.ACTION_PAUSE)) != 0;
2237                         if (isPlaying && canPause) {
2238                             cb.onPause();
2239                         } else if (!isPlaying && canPlay) {
2240                             cb.onPlay();
2241                         }
2242                         break;
2243                 }
2244             }
2245         }
2246     }
2247 
2248     static class MediaSessionImplApi21 implements MediaSessionImpl {
2249         private final Object mSessionObj;
2250         private final Token mToken;
2251 
2252         private PendingIntent mMediaButtonIntent;
2253 
MediaSessionImplApi21(Context context, String tag)2254         public MediaSessionImplApi21(Context context, String tag) {
2255             mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
2256             mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2257         }
2258 
MediaSessionImplApi21(Object mediaSession)2259         public MediaSessionImplApi21(Object mediaSession) {
2260             mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
2261             mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2262         }
2263 
2264         @Override
setCallback(Callback callback, Handler handler)2265         public void setCallback(Callback callback, Handler handler) {
2266             MediaSessionCompatApi21.setCallback(mSessionObj,
2267                     callback == null ? null : callback.mCallbackObj, handler);
2268         }
2269 
2270         @Override
setFlags(@essionFlags int flags)2271         public void setFlags(@SessionFlags int flags) {
2272             MediaSessionCompatApi21.setFlags(mSessionObj, flags);
2273         }
2274 
2275         @Override
setPlaybackToLocal(int stream)2276         public void setPlaybackToLocal(int stream) {
2277             MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
2278         }
2279 
2280         @Override
setPlaybackToRemote(VolumeProviderCompat volumeProvider)2281         public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
2282             MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
2283                     volumeProvider.getVolumeProvider());
2284         }
2285 
2286         @Override
setActive(boolean active)2287         public void setActive(boolean active) {
2288             MediaSessionCompatApi21.setActive(mSessionObj, active);
2289         }
2290 
2291         @Override
isActive()2292         public boolean isActive() {
2293             return MediaSessionCompatApi21.isActive(mSessionObj);
2294         }
2295 
2296         @Override
sendSessionEvent(String event, Bundle extras)2297         public void sendSessionEvent(String event, Bundle extras) {
2298             MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
2299         }
2300 
2301         @Override
release()2302         public void release() {
2303             MediaSessionCompatApi21.release(mSessionObj);
2304         }
2305 
2306         @Override
getSessionToken()2307         public Token getSessionToken() {
2308             return mToken;
2309         }
2310 
2311         @Override
setPlaybackState(PlaybackStateCompat state)2312         public void setPlaybackState(PlaybackStateCompat state) {
2313             MediaSessionCompatApi21.setPlaybackState(mSessionObj,
2314                     state == null ? null : state.getPlaybackState());
2315         }
2316 
2317         @Override
setMetadata(MediaMetadataCompat metadata)2318         public void setMetadata(MediaMetadataCompat metadata) {
2319             MediaSessionCompatApi21.setMetadata(mSessionObj,
2320                     metadata == null ? null : metadata.getMediaMetadata());
2321         }
2322 
2323         @Override
setSessionActivity(PendingIntent pi)2324         public void setSessionActivity(PendingIntent pi) {
2325             MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
2326         }
2327 
2328         @Override
setMediaButtonReceiver(PendingIntent mbr)2329         public void setMediaButtonReceiver(PendingIntent mbr) {
2330             mMediaButtonIntent = mbr;
2331             MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
2332         }
2333 
2334         @Override
setQueue(List<QueueItem> queue)2335         public void setQueue(List<QueueItem> queue) {
2336             List<Object> queueObjs = null;
2337             if (queue != null) {
2338                 queueObjs = new ArrayList<>();
2339                 for (QueueItem item : queue) {
2340                     queueObjs.add(item.getQueueItem());
2341                 }
2342             }
2343             MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
2344         }
2345 
2346         @Override
setQueueTitle(CharSequence title)2347         public void setQueueTitle(CharSequence title) {
2348             MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
2349         }
2350 
2351         @Override
setRatingType(@atingCompat.Style int type)2352         public void setRatingType(@RatingCompat.Style int type) {
2353             if (android.os.Build.VERSION.SDK_INT < 22) {
2354                 // TODO figure out 21 implementation
2355             } else {
2356                 MediaSessionCompatApi22.setRatingType(mSessionObj, type);
2357             }
2358         }
2359 
2360         @Override
setExtras(Bundle extras)2361         public void setExtras(Bundle extras) {
2362             MediaSessionCompatApi21.setExtras(mSessionObj, extras);
2363         }
2364 
2365         @Override
getMediaSession()2366         public Object getMediaSession() {
2367             return mSessionObj;
2368         }
2369 
2370         @Override
getRemoteControlClient()2371         public Object getRemoteControlClient() {
2372             return null;
2373         }
2374 
2375         @Override
getCallingPackage()2376         public String getCallingPackage() {
2377             if (android.os.Build.VERSION.SDK_INT < 24) {
2378                 return null;
2379             } else {
2380                 return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
2381             }
2382         }
2383     }
2384 }
2385