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