MediaController.java revision 031149cd5f22bd858142633c7a763450f42793f7
101fe661ae5da3739215d93922412df4b24c859a2RoboErik/*
201fe661ae5da3739215d93922412df4b24c859a2RoboErik * Copyright (C) 2014 The Android Open Source Project
301fe661ae5da3739215d93922412df4b24c859a2RoboErik *
401fe661ae5da3739215d93922412df4b24c859a2RoboErik * Licensed under the Apache License, Version 2.0 (the "License");
501fe661ae5da3739215d93922412df4b24c859a2RoboErik * you may not use this file except in compliance with the License.
601fe661ae5da3739215d93922412df4b24c859a2RoboErik * You may obtain a copy of the License at
701fe661ae5da3739215d93922412df4b24c859a2RoboErik *
801fe661ae5da3739215d93922412df4b24c859a2RoboErik *      http://www.apache.org/licenses/LICENSE-2.0
901fe661ae5da3739215d93922412df4b24c859a2RoboErik *
1001fe661ae5da3739215d93922412df4b24c859a2RoboErik * Unless required by applicable law or agreed to in writing, software
1101fe661ae5da3739215d93922412df4b24c859a2RoboErik * distributed under the License is distributed on an "AS IS" BASIS,
1201fe661ae5da3739215d93922412df4b24c859a2RoboErik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1301fe661ae5da3739215d93922412df4b24c859a2RoboErik * See the License for the specific language governing permissions and
1401fe661ae5da3739215d93922412df4b24c859a2RoboErik * limitations under the License.
1501fe661ae5da3739215d93922412df4b24c859a2RoboErik */
1601fe661ae5da3739215d93922412df4b24c859a2RoboErik
172f5b057da7d05d5d699a272aa24fd7c97cdda820RoboErikpackage android.media.session;
1801fe661ae5da3739215d93922412df4b24c859a2RoboErik
19bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brownimport android.annotation.NonNull;
20bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brownimport android.annotation.Nullable;
21e34c09daf89fb888fe2638e71758573462d85173RoboErikimport android.app.PendingIntent;
22031149cd5f22bd858142633c7a763450f42793f7RoboErikimport android.content.Context;
23f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Pealimport android.content.pm.ParceledListSlice;
249db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErikimport android.media.AudioAttributes;
2519c9518f6a817d53d5234de0020313cab6950b2fRoboErikimport android.media.AudioManager;
2642ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErikimport android.media.MediaMetadata;
27c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErikimport android.media.Rating;
28ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErikimport android.media.VolumeProvider;
291a937b04e63539cb1fab1bde601031d415c7156fJeff Brownimport android.media.routing.MediaRouter;
30f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Pealimport android.net.Uri;
3101fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Bundle;
3201fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Handler;
3301fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Looper;
3401fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Message;
3501fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.RemoteException;
368ae0f34db936a649ddaf9cdd086c224f6514efebRoboErikimport android.os.ResultReceiver;
3701fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.text.TextUtils;
3801fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.util.Log;
3901fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.view.KeyEvent;
4001fe661ae5da3739215d93922412df4b24c859a2RoboErik
418ae0f34db936a649ddaf9cdd086c224f6514efebRoboErikimport java.lang.ref.WeakReference;
4201fe661ae5da3739215d93922412df4b24c859a2RoboErikimport java.util.ArrayList;
43f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Pealimport java.util.List;
4401fe661ae5da3739215d93922412df4b24c859a2RoboErik
4501fe661ae5da3739215d93922412df4b24c859a2RoboErik/**
4601fe661ae5da3739215d93922412df4b24c859a2RoboErik * Allows an app to interact with an ongoing media session. Media buttons and
4701fe661ae5da3739215d93922412df4b24c859a2RoboErik * other commands can be sent to the session. A callback may be registered to
4801fe661ae5da3739215d93922412df4b24c859a2RoboErik * receive updates from the session, such as metadata and play state changes.
4901fe661ae5da3739215d93922412df4b24c859a2RoboErik * <p>
5042ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik * A MediaController can be created through {@link MediaSessionManager} if you
5101fe661ae5da3739215d93922412df4b24c859a2RoboErik * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
52dba34ba35cd2042d9a8fecfda56e2abe7a680badJeff Brown * you have a {@link MediaSession.Token} from the session owner.
5301fe661ae5da3739215d93922412df4b24c859a2RoboErik * <p>
5401fe661ae5da3739215d93922412df4b24c859a2RoboErik * MediaController objects are thread-safe.
5501fe661ae5da3739215d93922412df4b24c859a2RoboErik */
5642ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErikpublic final class MediaController {
57bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    private static final String TAG = "MediaController";
5801fe661ae5da3739215d93922412df4b24c859a2RoboErik
598ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private static final int MSG_EVENT = 1;
60c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
61c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    private static final int MSG_UPDATE_METADATA = 3;
6201a500ed1c6ae3fff66678144ae637aa8cad0eccJeff Brown    private static final int MSG_UPDATE_VOLUME = 4;
63f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    private static final int MSG_UPDATE_QUEUE = 5;
64f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    private static final int MSG_UPDATE_QUEUE_TITLE = 6;
65f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    private static final int MSG_UPDATE_EXTRAS = 7;
6601fe661ae5da3739215d93922412df4b24c859a2RoboErik
6707c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    private final ISessionController mSessionBinder;
6801fe661ae5da3739215d93922412df4b24c859a2RoboErik
6976fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    private final MediaSession.Token mToken;
70031149cd5f22bd858142633c7a763450f42793f7RoboErik    private final Context mContext;
718ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final CallbackStub mCbStub = new CallbackStub(this);
728ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
7301fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final Object mLock = new Object();
7401fe661ae5da3739215d93922412df4b24c859a2RoboErik
7501fe661ae5da3739215d93922412df4b24c859a2RoboErik    private boolean mCbRegistered = false;
76aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    private String mPackageName;
77aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    private String mTag;
7801fe661ae5da3739215d93922412df4b24c859a2RoboErik
791a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    private final TransportControls mTransportControls;
808ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
8101fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
828b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * Call for creating a MediaController directly from a binder. Should only
838b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * be used by framework code.
848b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     *
858ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @hide
8601fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
87031149cd5f22bd858142633c7a763450f42793f7RoboErik    public MediaController(Context context, ISessionController sessionBinder) {
888b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        if (sessionBinder == null) {
898b4bffcac996b4083e720310a09d315ca1c4a000RoboErik            throw new IllegalArgumentException("Session token cannot be null");
908b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        }
91031149cd5f22bd858142633c7a763450f42793f7RoboErik        if (context == null) {
92031149cd5f22bd858142633c7a763450f42793f7RoboErik            throw new IllegalArgumentException("Context cannot be null");
93031149cd5f22bd858142633c7a763450f42793f7RoboErik        }
948b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        mSessionBinder = sessionBinder;
958b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        mTransportControls = new TransportControls();
9676fca4e177e18b591439fdff64b8f5242a5122d0RoboErik        mToken = new MediaSession.Token(sessionBinder);
97031149cd5f22bd858142633c7a763450f42793f7RoboErik        mContext = context;
9801fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
9901fe661ae5da3739215d93922412df4b24c859a2RoboErik
10001fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
1018b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * Create a new MediaController from a session's token.
1028ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     *
103031149cd5f22bd858142633c7a763450f42793f7RoboErik     * @param context The caller's context.
1048b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * @param token The token for the session.
10501fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
106031149cd5f22bd858142633c7a763450f42793f7RoboErik    public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
107031149cd5f22bd858142633c7a763450f42793f7RoboErik        this(context, token.getBinder());
10801fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
10901fe661ae5da3739215d93922412df4b24c859a2RoboErik
11001fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
1111a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * Get a {@link TransportControls} instance to send transport actions to
1121a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * the associated session.
11301fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
1141a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * @return A transport controls instance.
11501fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
116bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @NonNull TransportControls getTransportControls() {
1171a937b04e63539cb1fab1bde601031d415c7156fJeff Brown        return mTransportControls;
1181a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    }
1191a937b04e63539cb1fab1bde601031d415c7156fJeff Brown
1201a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    /**
1211a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * Creates a media router delegate through which the destination of the media
1221a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * router may be observed and controlled.
1231a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     *
1241a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * @return The media router delegate, or null if the media session does
1251a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * not support media routing.
1261a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     */
1271a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    public @Nullable MediaRouter.Delegate createMediaRouterDelegate() {
1281a937b04e63539cb1fab1bde601031d415c7156fJeff Brown        return new MediaRouter.Delegate();
12901fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
13001fe661ae5da3739215d93922412df4b24c859a2RoboErik
13101fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
13279fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * Send the specified media button event to the session. Only media keys can
13379fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * be sent by this method, other keys will be ignored.
13401fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
13579fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * @param keyEvent The media button event to dispatch.
13679fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * @return true if the event was sent to the session, false otherwise.
13701fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
138bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
13979fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        if (keyEvent == null) {
14079fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            throw new IllegalArgumentException("KeyEvent may not be null");
14179fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        }
14279fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
14379fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            return false;
14401fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
14501fe661ae5da3739215d93922412df4b24c859a2RoboErik        try {
14679fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            return mSessionBinder.sendMediaButton(keyEvent);
14701fe661ae5da3739215d93922412df4b24c859a2RoboErik        } catch (RemoteException e) {
148c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            // System is dead. =(
14901fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
15079fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        return false;
15101fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
15201fe661ae5da3739215d93922412df4b24c859a2RoboErik
15301fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
154c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Get the current playback state for this session.
155c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     *
156c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * @return The current PlaybackState or null
157c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
158bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @Nullable PlaybackState getPlaybackState() {
159c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        try {
160c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return mSessionBinder.getPlaybackState();
161c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        } catch (RemoteException e) {
162c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            Log.wtf(TAG, "Error calling getPlaybackState.", e);
163c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return null;
164c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
165c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
166c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
167c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
168c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Get the current metadata for this session.
169c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     *
170c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * @return The current MediaMetadata or null.
171c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
172bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @Nullable MediaMetadata getMetadata() {
173c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        try {
174c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return mSessionBinder.getMetadata();
175c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        } catch (RemoteException e) {
176c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            Log.wtf(TAG, "Error calling getMetadata.", e);
177c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return null;
178c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
179c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
180c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
181c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
182f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     * Get the current play queue for this session.
183f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     *
184f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     * @return The current play queue or null.
185f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     */
186f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    public @Nullable List<MediaSession.Track> getQueue() {
187f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        try {
188f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            ParceledListSlice queue = mSessionBinder.getQueue();
189f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (queue != null) {
190f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                return queue.getList();
191f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
192f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        } catch (RemoteException e) {
193f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            Log.wtf(TAG, "Error calling getQueue.", e);
194f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
195f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        return null;
196f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    }
197f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
198f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    /**
199c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Get the rating type supported by the session. One of:
200c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <ul>
201c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_NONE}</li>
202c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_HEART}</li>
203c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
204c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_3_STARS}</li>
205c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_4_STARS}</li>
206c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_5_STARS}</li>
207c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_PERCENTAGE}</li>
208c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * </ul>
209c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     *
210c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * @return The supported rating type
211c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
212c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    public int getRatingType() {
213c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        try {
214c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return mSessionBinder.getRatingType();
215c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        } catch (RemoteException e) {
216c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            Log.wtf(TAG, "Error calling getRatingType.", e);
217c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return Rating.RATING_NONE;
218c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
219c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
220c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
221c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
22276fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     * Get the flags for this session. Flags are defined in {@link MediaSession}.
22373e23e229dd1a2d25687b1c6a63c708665378e41RoboErik     *
22473e23e229dd1a2d25687b1c6a63c708665378e41RoboErik     * @return The current set of flags for the session.
22573e23e229dd1a2d25687b1c6a63c708665378e41RoboErik     */
22676fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    public @MediaSession.SessionFlags long getFlags() {
22773e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        try {
22873e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            return mSessionBinder.getFlags();
22973e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        } catch (RemoteException e) {
23073e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            Log.wtf(TAG, "Error calling getFlags.", e);
23173e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        }
23273e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        return 0;
23373e23e229dd1a2d25687b1c6a63c708665378e41RoboErik    }
23473e23e229dd1a2d25687b1c6a63c708665378e41RoboErik
23573e23e229dd1a2d25687b1c6a63c708665378e41RoboErik    /**
236ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     * Get the current volume info for this session.
237ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     *
238ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     * @return The current volume info or null.
239ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     */
240bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @Nullable VolumeInfo getVolumeInfo() {
241ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        try {
242ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
2439db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik            return new VolumeInfo(result.volumeType, result.audioAttrs, result.controlType,
244ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik                    result.maxVolume, result.currentVolume);
245ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
246ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        } catch (RemoteException e) {
247ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            Log.wtf(TAG, "Error calling getVolumeInfo.", e);
248ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
249ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        return null;
250ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    }
251ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
252ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    /**
253e34c09daf89fb888fe2638e71758573462d85173RoboErik     * Get an intent for launching UI associated with this session if one
254e34c09daf89fb888fe2638e71758573462d85173RoboErik     * exists.
255e34c09daf89fb888fe2638e71758573462d85173RoboErik     *
256e34c09daf89fb888fe2638e71758573462d85173RoboErik     * @return A {@link PendingIntent} to launch UI or null.
257e34c09daf89fb888fe2638e71758573462d85173RoboErik     */
258e34c09daf89fb888fe2638e71758573462d85173RoboErik    public @Nullable PendingIntent getLaunchActivity() {
259e34c09daf89fb888fe2638e71758573462d85173RoboErik        try {
260e34c09daf89fb888fe2638e71758573462d85173RoboErik            return mSessionBinder.getLaunchPendingIntent();
261e34c09daf89fb888fe2638e71758573462d85173RoboErik        } catch (RemoteException e) {
262e34c09daf89fb888fe2638e71758573462d85173RoboErik            Log.wtf(TAG, "Error calling getPendingIntent.", e);
263e34c09daf89fb888fe2638e71758573462d85173RoboErik        }
264e34c09daf89fb888fe2638e71758573462d85173RoboErik        return null;
265e34c09daf89fb888fe2638e71758573462d85173RoboErik    }
266e34c09daf89fb888fe2638e71758573462d85173RoboErik
267e34c09daf89fb888fe2638e71758573462d85173RoboErik    /**
26876fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     * Get the token for the session this is connected to.
26976fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     *
27076fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     * @return The token for the connected session.
27176fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     */
27276fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    public @NonNull MediaSession.Token getSessionToken() {
27376fca4e177e18b591439fdff64b8f5242a5122d0RoboErik        return mToken;
27476fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    }
27576fca4e177e18b591439fdff64b8f5242a5122d0RoboErik
27676fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    /**
2779db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * Set the volume of the output this session is playing on. The command will
2789db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * be ignored if it does not support
27919c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
28019c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link AudioManager} may be used to affect the handling.
28119c9518f6a817d53d5234de0020313cab6950b2fRoboErik     *
28219c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @see #getVolumeInfo()
28319c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @param value The value to set it to, between 0 and the reported max.
28419c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @param flags Any flags to pass with the command.
28519c9518f6a817d53d5234de0020313cab6950b2fRoboErik     */
28619c9518f6a817d53d5234de0020313cab6950b2fRoboErik    public void setVolumeTo(int value, int flags) {
28719c9518f6a817d53d5234de0020313cab6950b2fRoboErik        try {
28819c9518f6a817d53d5234de0020313cab6950b2fRoboErik            mSessionBinder.setVolumeTo(value, flags);
28919c9518f6a817d53d5234de0020313cab6950b2fRoboErik        } catch (RemoteException e) {
29019c9518f6a817d53d5234de0020313cab6950b2fRoboErik            Log.wtf(TAG, "Error calling setVolumeTo.", e);
29119c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
29219c9518f6a817d53d5234de0020313cab6950b2fRoboErik    }
29319c9518f6a817d53d5234de0020313cab6950b2fRoboErik
29419c9518f6a817d53d5234de0020313cab6950b2fRoboErik    /**
2959db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * Adjust the volume of the output this session is playing on. The direction
2969db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * must be one of {@link AudioManager#ADJUST_LOWER},
2971ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
2981ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * The command will be ignored if the session does not support
2991ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
30019c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
30119c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link AudioManager} may be used to affect the handling.
30219c9518f6a817d53d5234de0020313cab6950b2fRoboErik     *
30319c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @see #getVolumeInfo()
3041ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * @param direction The direction to adjust the volume in.
30519c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @param flags Any flags to pass with the command.
30619c9518f6a817d53d5234de0020313cab6950b2fRoboErik     */
3071ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik    public void adjustVolume(int direction, int flags) {
30819c9518f6a817d53d5234de0020313cab6950b2fRoboErik        try {
3091ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik            mSessionBinder.adjustVolume(direction, flags);
31019c9518f6a817d53d5234de0020313cab6950b2fRoboErik        } catch (RemoteException e) {
31119c9518f6a817d53d5234de0020313cab6950b2fRoboErik            Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
31219c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
31319c9518f6a817d53d5234de0020313cab6950b2fRoboErik    }
31419c9518f6a817d53d5234de0020313cab6950b2fRoboErik
31519c9518f6a817d53d5234de0020313cab6950b2fRoboErik    /**
31601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Adds a callback to receive updates from the Session. Updates will be
31701fe661ae5da3739215d93922412df4b24c859a2RoboErik     * posted on the caller's thread.
31801fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
319bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     * @param callback The callback object, must not be null.
32001fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
321bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public void addCallback(@NonNull Callback callback) {
322bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        addCallback(callback, null);
32301fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
32401fe661ae5da3739215d93922412df4b24c859a2RoboErik
32501fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
32601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Adds a callback to receive updates from the session. Updates will be
3278ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * posted on the specified handler's thread.
32801fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
329bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     * @param callback The callback object, must not be null.
3308ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param handler The handler to post updates on. If null the callers thread
331bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     *            will be used.
33201fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
333bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
334bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        if (callback == null) {
335bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            throw new IllegalArgumentException("callback must not be null");
336bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        }
33701fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (handler == null) {
33801fe661ae5da3739215d93922412df4b24c859a2RoboErik            handler = new Handler();
33901fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
34001fe661ae5da3739215d93922412df4b24c859a2RoboErik        synchronized (mLock) {
341bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            addCallbackLocked(callback, handler);
34201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
34301fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
34401fe661ae5da3739215d93922412df4b24c859a2RoboErik
34501fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
34601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Stop receiving updates on the specified callback. If an update has
34701fe661ae5da3739215d93922412df4b24c859a2RoboErik     * already been posted you may still receive it after calling this method.
34801fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
349bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     * @param callback The callback to remove.
35001fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
351bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public void removeCallback(@NonNull Callback callback) {
352bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        if (callback == null) {
353bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            throw new IllegalArgumentException("callback must not be null");
354bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        }
35501fe661ae5da3739215d93922412df4b24c859a2RoboErik        synchronized (mLock) {
356bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            removeCallbackLocked(callback);
35701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
35801fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
35901fe661ae5da3739215d93922412df4b24c859a2RoboErik
3608ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    /**
3618ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Sends a generic command to the session. It is up to the session creator
3628ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * to decide what commands and parameters they will support. As such,
3638ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * commands should only be sent to sessions that the controller owns.
3648ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     *
3658ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param command The command to send
366f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal     * @param args Any parameters to include with the command
3678ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param cb The callback to receive the result on
3688ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     */
369f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal    public void sendCommand(@NonNull String command, @Nullable Bundle args,
370bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            @Nullable ResultReceiver cb) {
3718ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (TextUtils.isEmpty(command)) {
3728ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            throw new IllegalArgumentException("command cannot be null or empty");
3738ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
3748ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        try {
375f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            mSessionBinder.sendCommand(command, args, cb);
3768ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        } catch (RemoteException e) {
3778ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            Log.d(TAG, "Dead object in sendCommand.", e);
3788ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
3798ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
3808ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
38107c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    /**
382aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * Get the session owner's package name.
383fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik     *
384aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * @return The package name of of the session owner.
385aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     */
386aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    public String getPackageName() {
387aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        if (mPackageName == null) {
388aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik            try {
389aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                mPackageName = mSessionBinder.getPackageName();
390aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik            } catch (RemoteException e) {
391aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                Log.d(TAG, "Dead object in getPackageName.", e);
392aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik            }
393aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        }
394aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        return mPackageName;
395aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    }
396aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik
397aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    /**
398aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * Get the session's tag for debugging purposes.
399aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     *
400aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * @return The session's tag.
401fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik     * @hide
402fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik     */
403aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    public String getTag() {
404aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        if (mTag == null) {
40573e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            try {
406aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                mTag = mSessionBinder.getTag();
40773e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            } catch (RemoteException e) {
408aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                Log.d(TAG, "Dead object in getTag.", e);
40973e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            }
410fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik        }
411aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        return mTag;
412fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik    }
413fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik
41401fe661ae5da3739215d93922412df4b24c859a2RoboErik    /*
41501fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @hide
41601fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
41707c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    ISessionController getSessionBinder() {
41801fe661ae5da3739215d93922412df4b24c859a2RoboErik        return mSessionBinder;
41901fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
42001fe661ae5da3739215d93922412df4b24c859a2RoboErik
42101fe661ae5da3739215d93922412df4b24c859a2RoboErik    private void addCallbackLocked(Callback cb, Handler handler) {
4228ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (getHandlerForCallbackLocked(cb) != null) {
42301fe661ae5da3739215d93922412df4b24c859a2RoboErik            Log.w(TAG, "Callback is already added, ignoring");
42401fe661ae5da3739215d93922412df4b24c859a2RoboErik            return;
42501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
4268ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        MessageHandler holder = new MessageHandler(handler.getLooper(), cb);
4278ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        mCallbacks.add(holder);
42801fe661ae5da3739215d93922412df4b24c859a2RoboErik
42901fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (!mCbRegistered) {
43001fe661ae5da3739215d93922412df4b24c859a2RoboErik            try {
43101fe661ae5da3739215d93922412df4b24c859a2RoboErik                mSessionBinder.registerCallbackListener(mCbStub);
43201fe661ae5da3739215d93922412df4b24c859a2RoboErik                mCbRegistered = true;
43301fe661ae5da3739215d93922412df4b24c859a2RoboErik            } catch (RemoteException e) {
434d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                Log.e(TAG, "Dead object in registerCallback", e);
43501fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
43601fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
43701fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
43801fe661ae5da3739215d93922412df4b24c859a2RoboErik
4398ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private boolean removeCallbackLocked(Callback cb) {
440d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        boolean success = false;
4418ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
4428ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MessageHandler handler = mCallbacks.get(i);
4438ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (cb == handler.mCallback) {
4448ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                mCallbacks.remove(i);
445d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                success = true;
44601fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
44701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
448d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        if (mCbRegistered && mCallbacks.size() == 0) {
449d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            try {
450d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                mSessionBinder.unregisterCallbackListener(mCbStub);
451d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            } catch (RemoteException e) {
452d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                Log.e(TAG, "Dead object in removeCallbackLocked");
453d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            }
454d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            mCbRegistered = false;
455d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        }
456d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        return success;
45701fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
45801fe661ae5da3739215d93922412df4b24c859a2RoboErik
4598ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private MessageHandler getHandlerForCallbackLocked(Callback cb) {
4608ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (cb == null) {
4618ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            throw new IllegalArgumentException("Callback cannot be null");
46201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
4638ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
4648ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MessageHandler handler = mCallbacks.get(i);
4658ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (cb == handler.mCallback) {
4668ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                return handler;
4678ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
46801fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
4698ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return null;
47001fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
47101fe661ae5da3739215d93922412df4b24c859a2RoboErik
472c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    private final void postMessage(int what, Object obj, Bundle data) {
4738ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        synchronized (mLock) {
4748ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
475c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mCallbacks.get(i).post(what, obj, data);
4768ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
47701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
47801fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
47901fe661ae5da3739215d93922412df4b24c859a2RoboErik
48001fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
4818ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Callback for receiving updates on from the session. A Callback can be
4828ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * registered using {@link #addCallback}
48301fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
48401fe661ae5da3739215d93922412df4b24c859a2RoboErik    public static abstract class Callback {
48501fe661ae5da3739215d93922412df4b24c859a2RoboErik        /**
4868ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * Override to handle custom events sent by the session owner without a
4878ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * specified interface. Controllers should only handle these for
4888ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * sessions they own.
48901fe661ae5da3739215d93922412df4b24c859a2RoboErik         *
490bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown         * @param event The event from the session.
491bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown         * @param extras Optional parameters for the event, may be null.
49201fe661ae5da3739215d93922412df4b24c859a2RoboErik         */
493bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        public void onSessionEvent(@NonNull String event, @Nullable Bundle extras) {
49401fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
49501fe661ae5da3739215d93922412df4b24c859a2RoboErik
49601fe661ae5da3739215d93922412df4b24c859a2RoboErik        /**
497c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Override to handle changes in playback state.
498c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
499c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @param state The new playback state of the session
500c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
501bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        public void onPlaybackStateChanged(@NonNull PlaybackState state) {
502c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
503c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
504c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
505c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Override to handle changes to the current metadata.
506c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
507bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown         * @param metadata The current metadata for the session or null if none.
508c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @see MediaMetadata
509c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
510bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        public void onMetadataChanged(@Nullable MediaMetadata metadata) {
511c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
51219c9518f6a817d53d5234de0020313cab6950b2fRoboErik
51319c9518f6a817d53d5234de0020313cab6950b2fRoboErik        /**
514f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Override to handle changes to tracks in the queue.
515f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
516f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param queue A list of tracks in the current play queue. It should include the currently
517f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *              playing track as well as previous and upcoming tracks if applicable.
518f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @see MediaSession.Track
519f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
520f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueChanged(@Nullable List<MediaSession.Track> queue) {
521f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
522f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
523f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
524f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Override to handle changes to the queue title.
525f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
526f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param title The title that should be displayed along with the play queue such as
527f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *              "Now Playing". May be null if there is no such title.
528f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
529f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueTitleChanged(@Nullable CharSequence title) {
530f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
531f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
532f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
533f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Override to handle changes to the {@link MediaSession} extras.
534f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
535f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param extras The extras that can include other information associated with the
536f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *               {@link MediaSession}.
537f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
538f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onExtrasChanged(@Nullable Bundle extras) {
539f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
540f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
541f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
54219c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * Override to handle changes to the volume info.
54319c9518f6a817d53d5234de0020313cab6950b2fRoboErik         *
54419c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * @param info The current volume info for this session.
54519c9518f6a817d53d5234de0020313cab6950b2fRoboErik         */
54619c9518f6a817d53d5234de0020313cab6950b2fRoboErik        public void onVolumeInfoChanged(VolumeInfo info) {
54719c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
548c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
549c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
550c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
551c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Interface for controlling media playback on a session. This allows an app
552c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * to send media transport commands to the session.
553c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
554c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    public final class TransportControls {
555c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        private static final String TAG = "TransportController";
556c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
557c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        private TransportControls() {
558c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
559c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
560c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
561c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Request that the player start its playback at its current position.
562c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
563c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void play() {
564c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
565c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.play();
566c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
567c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling play.", e);
568c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
569c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
570c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
571c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
572f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Request that the player start playback for a specific {@link Uri}.
573f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
574f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param uri The uri of the requested media.
575f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param extras Optional extras that can include extra information about the media item
576f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *               to be played.
577f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
578f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void playUri(Uri uri, Bundle extras) {
579f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (uri == null) {
580f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                throw new IllegalArgumentException("You must specify a non-null Uri for playUri.");
581f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
582f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            try {
583f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                mSessionBinder.playUri(uri, extras);
584f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            } catch (RemoteException e) {
585f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                Log.wtf(TAG, "Error calling play(" + uri + ").", e);
586f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
587f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
588f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
589f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
590f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Request that the player start playback for a specific search query.
591f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
592f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param query The search query.
593f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param extras Optional extras that can include extra information about the query.
594f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
595f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void playFromSearch(String query, Bundle extras) {
596f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (TextUtils.isEmpty(query)) {
597f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                throw new IllegalArgumentException(
598f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                        "You must specify a non-empty search query for playFromSearch.");
599f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
600f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            try {
601f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                mSessionBinder.playFromSearch(query, extras);
602f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            } catch (RemoteException e) {
603f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                Log.wtf(TAG, "Error calling play(" + query + ").", e);
604f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
605f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
606f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
607f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
608f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Play a track with a specific id in the play queue.
609f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * If you specify an id that is not in the play queue, the behavior is undefined.
610f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
611f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void skipToTrack(long id) {
612f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            try {
613f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                mSessionBinder.skipToTrack(id);
614f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            } catch (RemoteException e) {
615f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                Log.wtf(TAG, "Error calling skipToTrack(" + id + ").", e);
616f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
617f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
618f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
619f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
620c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Request that the player pause its playback and stay at its current
621c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * position.
622c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
623c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void pause() {
624c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
625c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.pause();
626c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
627c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling pause.", e);
628c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
629c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
630c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
631c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
632c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Request that the player stop its playback; it may clear its state in
633c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * whatever way is appropriate.
634c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
635c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void stop() {
636c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
637c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.stop();
638c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
639c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling stop.", e);
640c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
641c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
642c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
643c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
644c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Move to a new location in the media stream.
645c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
646c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @param pos Position to move to, in milliseconds.
647c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
648c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void seekTo(long pos) {
649c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
650c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.seekTo(pos);
651c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
652c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling seekTo.", e);
653c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
654c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
655c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
656c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
657c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Start fast forwarding. If playback is already fast forwarding this
658c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * may increase the rate.
659c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
660c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void fastForward() {
661c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
662c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.fastForward();
663c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
664c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling fastForward.", e);
665c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
666c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
667c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
668c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
669c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Skip to the next item.
670c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
671c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void skipToNext() {
672c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
673c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.next();
674c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
675c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling next.", e);
676c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
677c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
678c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
679c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
680c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Start rewinding. If playback is already rewinding this may increase
681c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * the rate.
682c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
683c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void rewind() {
684c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
685c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.rewind();
686c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
687c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling rewind.", e);
688c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
689c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
690c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
691c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
692c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Skip to the previous item.
693c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
694c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void skipToPrevious() {
695c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
696c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.previous();
697c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
698c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling previous.", e);
699c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
700c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
701c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
702c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
703c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Rate the current content. This will cause the rating to be set for
704c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * the current user. The Rating type must match the type returned by
705c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * {@link #getRatingType()}.
706c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
707c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @param rating The rating to set for the current content
708c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
709c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void setRating(Rating rating) {
710c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
711c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.rate(rating);
712c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
713c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling rate.", e);
714c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
715c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
716f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal
717f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        /**
718f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * Send a custom action back for the {@link MediaSession} to perform.
719f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *
720f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param customAction The action to perform.
721f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param args Optional arguments to supply to the {@link MediaSession} for this
722f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *             custom action.
723f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         */
724f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        public void sendCustomAction(@NonNull PlaybackState.CustomAction customAction,
725f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                    @Nullable Bundle args) {
726f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            if (customAction == null) {
727f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                throw new IllegalArgumentException("CustomAction cannot be null.");
728f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            }
729f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            sendCustomAction(customAction.getAction(), args);
730f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        }
731f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal
732f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        /**
733f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * Send the id and args from a custom action back for the {@link MediaSession} to perform.
734f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *
735f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @see #sendCustomAction(PlaybackState.CustomAction action, Bundle args)
736f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param action The action identifier of the {@link PlaybackState.CustomAction} as
737f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *               specified by the {@link MediaSession}.
738f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param args Optional arguments to supply to the {@link MediaSession} for this
739f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *             custom action.
740f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         */
741f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        public void sendCustomAction(@NonNull String action, @Nullable Bundle args) {
742f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            if (TextUtils.isEmpty(action)) {
743f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                throw new IllegalArgumentException("CustomAction cannot be null.");
744f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            }
745f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            try {
746f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                mSessionBinder.sendCustomAction(action, args);
747f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            } catch (RemoteException e) {
748f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                Log.d(TAG, "Dead object in sendCustomAction.", e);
749f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            }
750f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        }
7518ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
75201fe661ae5da3739215d93922412df4b24c859a2RoboErik
753ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    /**
754ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     * Holds information about the way volume is handled for this session.
755ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     */
756ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    public static final class VolumeInfo {
757ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mVolumeType;
758ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mVolumeControl;
759ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mMaxVolume;
760ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mCurrentVolume;
7619db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik        private final AudioAttributes mAudioAttrs;
762ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
763ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
764ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @hide
765ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
7669db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik        public VolumeInfo(int type, AudioAttributes attrs, int control, int max, int current) {
767ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mVolumeType = type;
7689db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik            mAudioAttrs = attrs;
769ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mVolumeControl = control;
770ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mMaxVolume = max;
771ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mCurrentVolume = current;
772ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
773ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
774ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
775ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the type of volume handling, either local or remote. One of:
776ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <ul>
77719c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * <li>{@link MediaSession#PLAYBACK_TYPE_LOCAL}</li>
77819c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * <li>{@link MediaSession#PLAYBACK_TYPE_REMOTE}</li>
779ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * </ul>
780ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
781ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The type of volume handling this session is using.
782ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
783ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getVolumeType() {
784ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mVolumeType;
785ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
786ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
787ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
7889db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * Get the audio attributes for this session. The attributes will affect
7899db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * volume handling for the session. When the volume type is
7909db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * {@link MediaSession#PLAYBACK_TYPE_REMOTE} these may be ignored by the
7919db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * remote volume handler.
792ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
7939db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * @return The attributes for this session.
794ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
7959db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik        public AudioAttributes getAudioAttributes() {
7969db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik            return mAudioAttrs;
797ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
798ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
799ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
800ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the type of volume control that can be used. One of:
801ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <ul>
802ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
803ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
804ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
805ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * </ul>
806ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
807ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The type of volume control that may be used with this
808ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *         session.
809ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
810ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getVolumeControl() {
811ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mVolumeControl;
812ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
813ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
814ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
815ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the maximum volume that may be set for this session.
816ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
817ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The maximum allowed volume where this session is playing.
818ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
819ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getMaxVolume() {
820ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mMaxVolume;
821ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
822ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
823ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
824ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the current volume for this session.
825ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
826ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The current volume where this session is playing.
827ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
828ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getCurrentVolume() {
829ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mCurrentVolume;
830ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
831ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    }
832ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
83307c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    private final static class CallbackStub extends ISessionControllerCallback.Stub {
83442ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        private final WeakReference<MediaController> mController;
83501fe661ae5da3739215d93922412df4b24c859a2RoboErik
83642ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        public CallbackStub(MediaController controller) {
83742ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            mController = new WeakReference<MediaController>(controller);
83801fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
83901fe661ae5da3739215d93922412df4b24c859a2RoboErik
84001fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
8418ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onEvent(String event, Bundle extras) {
84242ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            MediaController controller = mController.get();
8438ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
844c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                controller.postMessage(MSG_EVENT, event, extras);
84501fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
84601fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
84701fe661ae5da3739215d93922412df4b24c859a2RoboErik
84801fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
8498ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onPlaybackStateChanged(PlaybackState state) {
85042ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            MediaController controller = mController.get();
8518ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
852c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null);
85301fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
85401fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
85501fe661ae5da3739215d93922412df4b24c859a2RoboErik
85601fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
8578ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onMetadataChanged(MediaMetadata metadata) {
85842ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            MediaController controller = mController.get();
8598ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
860c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                controller.postMessage(MSG_UPDATE_METADATA, metadata, null);
86101fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
86201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
86301fe661ae5da3739215d93922412df4b24c859a2RoboErik
86419c9518f6a817d53d5234de0020313cab6950b2fRoboErik        @Override
865f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueChanged(ParceledListSlice parceledQueue) {
866f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            List<MediaSession.Track> queue = parceledQueue.getList();
867f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            MediaController controller = mController.get();
868f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (controller != null) {
869f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
870f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
871f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
872f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
873f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        @Override
874f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueTitleChanged(CharSequence title) {
875f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            MediaController controller = mController.get();
876f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (controller != null) {
877f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                controller.postMessage(MSG_UPDATE_QUEUE_TITLE, title, null);
878f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
879f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
880f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
881f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        @Override
882f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onExtrasChanged(Bundle extras) {
883f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            MediaController controller = mController.get();
884f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (controller != null) {
885f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                controller.postMessage(MSG_UPDATE_EXTRAS, extras, null);
886f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
887f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
888f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
889f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        @Override
89019c9518f6a817d53d5234de0020313cab6950b2fRoboErik        public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
89119c9518f6a817d53d5234de0020313cab6950b2fRoboErik            MediaController controller = mController.get();
89219c9518f6a817d53d5234de0020313cab6950b2fRoboErik            if (controller != null) {
8939db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik                VolumeInfo info = new VolumeInfo(pvi.volumeType, pvi.audioAttrs, pvi.controlType,
89419c9518f6a817d53d5234de0020313cab6950b2fRoboErik                        pvi.maxVolume, pvi.currentVolume);
89519c9518f6a817d53d5234de0020313cab6950b2fRoboErik                controller.postMessage(MSG_UPDATE_VOLUME, info, null);
89619c9518f6a817d53d5234de0020313cab6950b2fRoboErik            }
89719c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
89819c9518f6a817d53d5234de0020313cab6950b2fRoboErik
89901fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
90001fe661ae5da3739215d93922412df4b24c859a2RoboErik
90101fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final static class MessageHandler extends Handler {
90242ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        private final MediaController.Callback mCallback;
90301fe661ae5da3739215d93922412df4b24c859a2RoboErik
90442ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        public MessageHandler(Looper looper, MediaController.Callback cb) {
9058ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            super(looper, null, true);
9068ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            mCallback = cb;
90701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
90801fe661ae5da3739215d93922412df4b24c859a2RoboErik
90901fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
91001fe661ae5da3739215d93922412df4b24c859a2RoboErik        public void handleMessage(Message msg) {
91101fe661ae5da3739215d93922412df4b24c859a2RoboErik            switch (msg.what) {
9128ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                case MSG_EVENT:
91379fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik                    mCallback.onSessionEvent((String) msg.obj, msg.getData());
91401fe661ae5da3739215d93922412df4b24c859a2RoboErik                    break;
915c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                case MSG_UPDATE_PLAYBACK_STATE:
916c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
917c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    break;
918c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                case MSG_UPDATE_METADATA:
919c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    mCallback.onMetadataChanged((MediaMetadata) msg.obj);
920c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    break;
921f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                case MSG_UPDATE_QUEUE:
922f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    mCallback.onQueueChanged((List<MediaSession.Track>) msg.obj);
923f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    break;
924f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                case MSG_UPDATE_QUEUE_TITLE:
925f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    mCallback.onQueueTitleChanged((CharSequence) msg.obj);
926f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    break;
927f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                case MSG_UPDATE_EXTRAS:
928f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    mCallback.onExtrasChanged((Bundle) msg.obj);
929f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    break;
93019c9518f6a817d53d5234de0020313cab6950b2fRoboErik                case MSG_UPDATE_VOLUME:
93119c9518f6a817d53d5234de0020313cab6950b2fRoboErik                    mCallback.onVolumeInfoChanged((VolumeInfo) msg.obj);
93219c9518f6a817d53d5234de0020313cab6950b2fRoboErik                    break;
93301fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
93401fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
9358ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
9368ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void post(int what, Object obj, Bundle data) {
9378ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            obtainMessage(what, obj).sendToTarget();
9388ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
93901fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
94001fe661ae5da3739215d93922412df4b24c859a2RoboErik
94101fe661ae5da3739215d93922412df4b24c859a2RoboErik}
942