MediaController.java revision aa4e23bbb36994708ba72c5f4c83255025d99e07
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;
22f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Pealimport android.content.pm.ParceledListSlice;
239db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErikimport android.media.AudioAttributes;
2419c9518f6a817d53d5234de0020313cab6950b2fRoboErikimport android.media.AudioManager;
2542ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErikimport android.media.MediaMetadata;
26c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErikimport android.media.Rating;
27ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErikimport android.media.VolumeProvider;
281a937b04e63539cb1fab1bde601031d415c7156fJeff Brownimport android.media.routing.MediaRouter;
29f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Pealimport android.net.Uri;
3001fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Bundle;
3101fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Handler;
3201fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Looper;
3301fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Message;
3401fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.RemoteException;
358ae0f34db936a649ddaf9cdd086c224f6514efebRoboErikimport android.os.ResultReceiver;
3601fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.text.TextUtils;
3701fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.util.Log;
3801fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.view.KeyEvent;
3901fe661ae5da3739215d93922412df4b24c859a2RoboErik
408ae0f34db936a649ddaf9cdd086c224f6514efebRoboErikimport java.lang.ref.WeakReference;
4101fe661ae5da3739215d93922412df4b24c859a2RoboErikimport java.util.ArrayList;
42f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Pealimport java.util.List;
4301fe661ae5da3739215d93922412df4b24c859a2RoboErik
4401fe661ae5da3739215d93922412df4b24c859a2RoboErik/**
4501fe661ae5da3739215d93922412df4b24c859a2RoboErik * Allows an app to interact with an ongoing media session. Media buttons and
4601fe661ae5da3739215d93922412df4b24c859a2RoboErik * other commands can be sent to the session. A callback may be registered to
4701fe661ae5da3739215d93922412df4b24c859a2RoboErik * receive updates from the session, such as metadata and play state changes.
4801fe661ae5da3739215d93922412df4b24c859a2RoboErik * <p>
4942ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik * A MediaController can be created through {@link MediaSessionManager} if you
5001fe661ae5da3739215d93922412df4b24c859a2RoboErik * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
51dba34ba35cd2042d9a8fecfda56e2abe7a680badJeff Brown * you have a {@link MediaSession.Token} from the session owner.
5201fe661ae5da3739215d93922412df4b24c859a2RoboErik * <p>
5301fe661ae5da3739215d93922412df4b24c859a2RoboErik * MediaController objects are thread-safe.
5401fe661ae5da3739215d93922412df4b24c859a2RoboErik */
5542ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErikpublic final class MediaController {
56bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    private static final String TAG = "MediaController";
5701fe661ae5da3739215d93922412df4b24c859a2RoboErik
588ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private static final int MSG_EVENT = 1;
59c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
60c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    private static final int MSG_UPDATE_METADATA = 3;
6101a500ed1c6ae3fff66678144ae637aa8cad0eccJeff Brown    private static final int MSG_UPDATE_VOLUME = 4;
62f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    private static final int MSG_UPDATE_QUEUE = 5;
63f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    private static final int MSG_UPDATE_QUEUE_TITLE = 6;
64f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    private static final int MSG_UPDATE_EXTRAS = 7;
6501fe661ae5da3739215d93922412df4b24c859a2RoboErik
6607c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    private final ISessionController mSessionBinder;
6701fe661ae5da3739215d93922412df4b24c859a2RoboErik
6876fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    private final MediaSession.Token mToken;
698ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final CallbackStub mCbStub = new CallbackStub(this);
708ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
7101fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final Object mLock = new Object();
7201fe661ae5da3739215d93922412df4b24c859a2RoboErik
7301fe661ae5da3739215d93922412df4b24c859a2RoboErik    private boolean mCbRegistered = false;
74aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    private String mPackageName;
75aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    private String mTag;
7601fe661ae5da3739215d93922412df4b24c859a2RoboErik
771a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    private final TransportControls mTransportControls;
788ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
7901fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
808b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * Call for creating a MediaController directly from a binder. Should only
818b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * be used by framework code.
828b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     *
838ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @hide
8401fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
858b4bffcac996b4083e720310a09d315ca1c4a000RoboErik    public MediaController(ISessionController sessionBinder) {
868b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        if (sessionBinder == null) {
878b4bffcac996b4083e720310a09d315ca1c4a000RoboErik            throw new IllegalArgumentException("Session token cannot be null");
888b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        }
898b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        mSessionBinder = sessionBinder;
908b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        mTransportControls = new TransportControls();
9176fca4e177e18b591439fdff64b8f5242a5122d0RoboErik        mToken = new MediaSession.Token(sessionBinder);
9201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
9301fe661ae5da3739215d93922412df4b24c859a2RoboErik
9401fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
958b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * Create a new MediaController from a session's token.
968ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     *
978b4bffcac996b4083e720310a09d315ca1c4a000RoboErik     * @param token The token for the session.
9801fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
998b4bffcac996b4083e720310a09d315ca1c4a000RoboErik    public MediaController(@NonNull MediaSession.Token token) {
1008b4bffcac996b4083e720310a09d315ca1c4a000RoboErik        this(token.getBinder());
10101fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
10201fe661ae5da3739215d93922412df4b24c859a2RoboErik
10301fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
1041a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * Get a {@link TransportControls} instance to send transport actions to
1051a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * the associated session.
10601fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
1071a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * @return A transport controls instance.
10801fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
109bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @NonNull TransportControls getTransportControls() {
1101a937b04e63539cb1fab1bde601031d415c7156fJeff Brown        return mTransportControls;
1111a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    }
1121a937b04e63539cb1fab1bde601031d415c7156fJeff Brown
1131a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    /**
1141a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * Creates a media router delegate through which the destination of the media
1151a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * router may be observed and controlled.
1161a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     *
1171a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * @return The media router delegate, or null if the media session does
1181a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     * not support media routing.
1191a937b04e63539cb1fab1bde601031d415c7156fJeff Brown     */
1201a937b04e63539cb1fab1bde601031d415c7156fJeff Brown    public @Nullable MediaRouter.Delegate createMediaRouterDelegate() {
1211a937b04e63539cb1fab1bde601031d415c7156fJeff Brown        return new MediaRouter.Delegate();
12201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
12301fe661ae5da3739215d93922412df4b24c859a2RoboErik
12401fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
12579fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * Send the specified media button event to the session. Only media keys can
12679fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * be sent by this method, other keys will be ignored.
12701fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
12879fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * @param keyEvent The media button event to dispatch.
12979fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik     * @return true if the event was sent to the session, false otherwise.
13001fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
131bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
13279fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        if (keyEvent == null) {
13379fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            throw new IllegalArgumentException("KeyEvent may not be null");
13479fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        }
13579fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
13679fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            return false;
13701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
13801fe661ae5da3739215d93922412df4b24c859a2RoboErik        try {
13979fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            return mSessionBinder.sendMediaButton(keyEvent);
14001fe661ae5da3739215d93922412df4b24c859a2RoboErik        } catch (RemoteException e) {
141c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            // System is dead. =(
14201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
14379fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik        return false;
14401fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
14501fe661ae5da3739215d93922412df4b24c859a2RoboErik
14601fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
147c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Get the current playback state for this session.
148c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     *
149c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * @return The current PlaybackState or null
150c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
151bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @Nullable PlaybackState getPlaybackState() {
152c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        try {
153c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return mSessionBinder.getPlaybackState();
154c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        } catch (RemoteException e) {
155c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            Log.wtf(TAG, "Error calling getPlaybackState.", e);
156c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return null;
157c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
158c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
159c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
160c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
161c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Get the current metadata for this session.
162c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     *
163c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * @return The current MediaMetadata or null.
164c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
165bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @Nullable MediaMetadata getMetadata() {
166c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        try {
167c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return mSessionBinder.getMetadata();
168c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        } catch (RemoteException e) {
169c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            Log.wtf(TAG, "Error calling getMetadata.", e);
170c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return null;
171c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
172c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
173c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
174c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
175f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     * Get the current play queue for this session.
176f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     *
177f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     * @return The current play queue or null.
178f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal     */
179f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    public @Nullable List<MediaSession.Track> getQueue() {
180f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        try {
181f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            ParceledListSlice queue = mSessionBinder.getQueue();
182f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (queue != null) {
183f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                return queue.getList();
184f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
185f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        } catch (RemoteException e) {
186f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            Log.wtf(TAG, "Error calling getQueue.", e);
187f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
188f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        return null;
189f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    }
190f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
191f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal    /**
192c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Get the rating type supported by the session. One of:
193c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <ul>
194c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_NONE}</li>
195c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_HEART}</li>
196c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
197c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_3_STARS}</li>
198c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_4_STARS}</li>
199c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_5_STARS}</li>
200c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * <li>{@link Rating#RATING_PERCENTAGE}</li>
201c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * </ul>
202c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     *
203c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * @return The supported rating type
204c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
205c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    public int getRatingType() {
206c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        try {
207c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return mSessionBinder.getRatingType();
208c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        } catch (RemoteException e) {
209c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            Log.wtf(TAG, "Error calling getRatingType.", e);
210c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            return Rating.RATING_NONE;
211c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
212c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
213c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
214c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
21576fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     * Get the flags for this session. Flags are defined in {@link MediaSession}.
21673e23e229dd1a2d25687b1c6a63c708665378e41RoboErik     *
21773e23e229dd1a2d25687b1c6a63c708665378e41RoboErik     * @return The current set of flags for the session.
21873e23e229dd1a2d25687b1c6a63c708665378e41RoboErik     */
21976fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    public @MediaSession.SessionFlags long getFlags() {
22073e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        try {
22173e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            return mSessionBinder.getFlags();
22273e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        } catch (RemoteException e) {
22373e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            Log.wtf(TAG, "Error calling getFlags.", e);
22473e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        }
22573e23e229dd1a2d25687b1c6a63c708665378e41RoboErik        return 0;
22673e23e229dd1a2d25687b1c6a63c708665378e41RoboErik    }
22773e23e229dd1a2d25687b1c6a63c708665378e41RoboErik
22873e23e229dd1a2d25687b1c6a63c708665378e41RoboErik    /**
229ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     * Get the current volume info for this session.
230ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     *
231ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     * @return The current volume info or null.
232ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     */
233bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public @Nullable VolumeInfo getVolumeInfo() {
234ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        try {
235ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
2369db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik            return new VolumeInfo(result.volumeType, result.audioAttrs, result.controlType,
237ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik                    result.maxVolume, result.currentVolume);
238ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
239ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        } catch (RemoteException e) {
240ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            Log.wtf(TAG, "Error calling getVolumeInfo.", e);
241ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
242ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        return null;
243ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    }
244ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
245ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    /**
246e34c09daf89fb888fe2638e71758573462d85173RoboErik     * Get an intent for launching UI associated with this session if one
247e34c09daf89fb888fe2638e71758573462d85173RoboErik     * exists.
248e34c09daf89fb888fe2638e71758573462d85173RoboErik     *
249e34c09daf89fb888fe2638e71758573462d85173RoboErik     * @return A {@link PendingIntent} to launch UI or null.
250e34c09daf89fb888fe2638e71758573462d85173RoboErik     */
251e34c09daf89fb888fe2638e71758573462d85173RoboErik    public @Nullable PendingIntent getLaunchActivity() {
252e34c09daf89fb888fe2638e71758573462d85173RoboErik        try {
253e34c09daf89fb888fe2638e71758573462d85173RoboErik            return mSessionBinder.getLaunchPendingIntent();
254e34c09daf89fb888fe2638e71758573462d85173RoboErik        } catch (RemoteException e) {
255e34c09daf89fb888fe2638e71758573462d85173RoboErik            Log.wtf(TAG, "Error calling getPendingIntent.", e);
256e34c09daf89fb888fe2638e71758573462d85173RoboErik        }
257e34c09daf89fb888fe2638e71758573462d85173RoboErik        return null;
258e34c09daf89fb888fe2638e71758573462d85173RoboErik    }
259e34c09daf89fb888fe2638e71758573462d85173RoboErik
260e34c09daf89fb888fe2638e71758573462d85173RoboErik    /**
26176fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     * Get the token for the session this is connected to.
26276fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     *
26376fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     * @return The token for the connected session.
26476fca4e177e18b591439fdff64b8f5242a5122d0RoboErik     */
26576fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    public @NonNull MediaSession.Token getSessionToken() {
26676fca4e177e18b591439fdff64b8f5242a5122d0RoboErik        return mToken;
26776fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    }
26876fca4e177e18b591439fdff64b8f5242a5122d0RoboErik
26976fca4e177e18b591439fdff64b8f5242a5122d0RoboErik    /**
2709db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * Set the volume of the output this session is playing on. The command will
2719db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * be ignored if it does not support
27219c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
27319c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link AudioManager} may be used to affect the handling.
27419c9518f6a817d53d5234de0020313cab6950b2fRoboErik     *
27519c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @see #getVolumeInfo()
27619c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @param value The value to set it to, between 0 and the reported max.
27719c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @param flags Any flags to pass with the command.
27819c9518f6a817d53d5234de0020313cab6950b2fRoboErik     */
27919c9518f6a817d53d5234de0020313cab6950b2fRoboErik    public void setVolumeTo(int value, int flags) {
28019c9518f6a817d53d5234de0020313cab6950b2fRoboErik        try {
28119c9518f6a817d53d5234de0020313cab6950b2fRoboErik            mSessionBinder.setVolumeTo(value, flags);
28219c9518f6a817d53d5234de0020313cab6950b2fRoboErik        } catch (RemoteException e) {
28319c9518f6a817d53d5234de0020313cab6950b2fRoboErik            Log.wtf(TAG, "Error calling setVolumeTo.", e);
28419c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
28519c9518f6a817d53d5234de0020313cab6950b2fRoboErik    }
28619c9518f6a817d53d5234de0020313cab6950b2fRoboErik
28719c9518f6a817d53d5234de0020313cab6950b2fRoboErik    /**
2889db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * Adjust the volume of the output this session is playing on. The direction
2899db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik     * must be one of {@link AudioManager#ADJUST_LOWER},
2901ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
2911ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * The command will be ignored if the session does not support
2921ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
29319c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
29419c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * {@link AudioManager} may be used to affect the handling.
29519c9518f6a817d53d5234de0020313cab6950b2fRoboErik     *
29619c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @see #getVolumeInfo()
2971ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik     * @param direction The direction to adjust the volume in.
29819c9518f6a817d53d5234de0020313cab6950b2fRoboErik     * @param flags Any flags to pass with the command.
29919c9518f6a817d53d5234de0020313cab6950b2fRoboErik     */
3001ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik    public void adjustVolume(int direction, int flags) {
30119c9518f6a817d53d5234de0020313cab6950b2fRoboErik        try {
3021ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik            mSessionBinder.adjustVolume(direction, flags);
30319c9518f6a817d53d5234de0020313cab6950b2fRoboErik        } catch (RemoteException e) {
30419c9518f6a817d53d5234de0020313cab6950b2fRoboErik            Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
30519c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
30619c9518f6a817d53d5234de0020313cab6950b2fRoboErik    }
30719c9518f6a817d53d5234de0020313cab6950b2fRoboErik
30819c9518f6a817d53d5234de0020313cab6950b2fRoboErik    /**
30901fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Adds a callback to receive updates from the Session. Updates will be
31001fe661ae5da3739215d93922412df4b24c859a2RoboErik     * posted on the caller's thread.
31101fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
312bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     * @param callback The callback object, must not be null.
31301fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
314bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public void addCallback(@NonNull Callback callback) {
315bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        addCallback(callback, null);
31601fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
31701fe661ae5da3739215d93922412df4b24c859a2RoboErik
31801fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
31901fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Adds a callback to receive updates from the session. Updates will be
3208ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * posted on the specified handler's thread.
32101fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
322bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     * @param callback The callback object, must not be null.
3238ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param handler The handler to post updates on. If null the callers thread
324bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     *            will be used.
32501fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
326bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
327bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        if (callback == null) {
328bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            throw new IllegalArgumentException("callback must not be null");
329bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        }
33001fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (handler == null) {
33101fe661ae5da3739215d93922412df4b24c859a2RoboErik            handler = new Handler();
33201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
33301fe661ae5da3739215d93922412df4b24c859a2RoboErik        synchronized (mLock) {
334bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            addCallbackLocked(callback, handler);
33501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
33601fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
33701fe661ae5da3739215d93922412df4b24c859a2RoboErik
33801fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
33901fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Stop receiving updates on the specified callback. If an update has
34001fe661ae5da3739215d93922412df4b24c859a2RoboErik     * already been posted you may still receive it after calling this method.
34101fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
342bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown     * @param callback The callback to remove.
34301fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
344bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown    public void removeCallback(@NonNull Callback callback) {
345bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        if (callback == null) {
346bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            throw new IllegalArgumentException("callback must not be null");
347bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        }
34801fe661ae5da3739215d93922412df4b24c859a2RoboErik        synchronized (mLock) {
349bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            removeCallbackLocked(callback);
35001fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
35101fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
35201fe661ae5da3739215d93922412df4b24c859a2RoboErik
3538ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    /**
3548ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Sends a generic command to the session. It is up to the session creator
3558ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * to decide what commands and parameters they will support. As such,
3568ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * commands should only be sent to sessions that the controller owns.
3578ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     *
3588ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param command The command to send
359f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal     * @param args Any parameters to include with the command
3608ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param cb The callback to receive the result on
3618ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     */
362f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal    public void sendCommand(@NonNull String command, @Nullable Bundle args,
363bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown            @Nullable ResultReceiver cb) {
3648ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (TextUtils.isEmpty(command)) {
3658ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            throw new IllegalArgumentException("command cannot be null or empty");
3668ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
3678ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        try {
368f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            mSessionBinder.sendCommand(command, args, cb);
3698ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        } catch (RemoteException e) {
3708ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            Log.d(TAG, "Dead object in sendCommand.", e);
3718ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
3728ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
3738ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
37407c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    /**
375aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * Get the session owner's package name.
376fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik     *
377aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * @return The package name of of the session owner.
378aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     */
379aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    public String getPackageName() {
380aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        if (mPackageName == null) {
381aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik            try {
382aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                mPackageName = mSessionBinder.getPackageName();
383aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik            } catch (RemoteException e) {
384aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                Log.d(TAG, "Dead object in getPackageName.", e);
385aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik            }
386aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        }
387aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        return mPackageName;
388aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    }
389aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik
390aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    /**
391aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * Get the session's tag for debugging purposes.
392aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     *
393aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik     * @return The session's tag.
394fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik     * @hide
395fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik     */
396aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik    public String getTag() {
397aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        if (mTag == null) {
39873e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            try {
399aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                mTag = mSessionBinder.getTag();
40073e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            } catch (RemoteException e) {
401aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik                Log.d(TAG, "Dead object in getTag.", e);
40273e23e229dd1a2d25687b1c6a63c708665378e41RoboErik            }
403fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik        }
404aa4e23bbb36994708ba72c5f4c83255025d99e07RoboErik        return mTag;
405fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik    }
406fb442b03840245c7e52cf2a540a77c5fc6c54587RoboErik
40701fe661ae5da3739215d93922412df4b24c859a2RoboErik    /*
40801fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @hide
40901fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
41007c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    ISessionController getSessionBinder() {
41101fe661ae5da3739215d93922412df4b24c859a2RoboErik        return mSessionBinder;
41201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
41301fe661ae5da3739215d93922412df4b24c859a2RoboErik
41401fe661ae5da3739215d93922412df4b24c859a2RoboErik    private void addCallbackLocked(Callback cb, Handler handler) {
4158ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (getHandlerForCallbackLocked(cb) != null) {
41601fe661ae5da3739215d93922412df4b24c859a2RoboErik            Log.w(TAG, "Callback is already added, ignoring");
41701fe661ae5da3739215d93922412df4b24c859a2RoboErik            return;
41801fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
4198ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        MessageHandler holder = new MessageHandler(handler.getLooper(), cb);
4208ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        mCallbacks.add(holder);
42101fe661ae5da3739215d93922412df4b24c859a2RoboErik
42201fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (!mCbRegistered) {
42301fe661ae5da3739215d93922412df4b24c859a2RoboErik            try {
42401fe661ae5da3739215d93922412df4b24c859a2RoboErik                mSessionBinder.registerCallbackListener(mCbStub);
42501fe661ae5da3739215d93922412df4b24c859a2RoboErik                mCbRegistered = true;
42601fe661ae5da3739215d93922412df4b24c859a2RoboErik            } catch (RemoteException e) {
427d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                Log.e(TAG, "Dead object in registerCallback", e);
42801fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
42901fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
43001fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
43101fe661ae5da3739215d93922412df4b24c859a2RoboErik
4328ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private boolean removeCallbackLocked(Callback cb) {
433d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        boolean success = false;
4348ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
4358ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MessageHandler handler = mCallbacks.get(i);
4368ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (cb == handler.mCallback) {
4378ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                mCallbacks.remove(i);
438d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                success = true;
43901fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
44001fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
441d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        if (mCbRegistered && mCallbacks.size() == 0) {
442d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            try {
443d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                mSessionBinder.unregisterCallbackListener(mCbStub);
444d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            } catch (RemoteException e) {
445d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik                Log.e(TAG, "Dead object in removeCallbackLocked");
446d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            }
447d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik            mCbRegistered = false;
448d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        }
449d3c8642dae9a1f6db60e2f8e5c7b32cd1b3169dfRoboErik        return success;
45001fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
45101fe661ae5da3739215d93922412df4b24c859a2RoboErik
4528ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private MessageHandler getHandlerForCallbackLocked(Callback cb) {
4538ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (cb == null) {
4548ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            throw new IllegalArgumentException("Callback cannot be null");
45501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
4568ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
4578ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MessageHandler handler = mCallbacks.get(i);
4588ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (cb == handler.mCallback) {
4598ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                return handler;
4608ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
46101fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
4628ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return null;
46301fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
46401fe661ae5da3739215d93922412df4b24c859a2RoboErik
465c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    private final void postMessage(int what, Object obj, Bundle data) {
4668ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        synchronized (mLock) {
4678ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
468c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mCallbacks.get(i).post(what, obj, data);
4698ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
47001fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
47101fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
47201fe661ae5da3739215d93922412df4b24c859a2RoboErik
47301fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
4748ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Callback for receiving updates on from the session. A Callback can be
4758ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * registered using {@link #addCallback}
47601fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
47701fe661ae5da3739215d93922412df4b24c859a2RoboErik    public static abstract class Callback {
47801fe661ae5da3739215d93922412df4b24c859a2RoboErik        /**
4798ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * Override to handle custom events sent by the session owner without a
4808ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * specified interface. Controllers should only handle these for
4818ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * sessions they own.
48201fe661ae5da3739215d93922412df4b24c859a2RoboErik         *
483bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown         * @param event The event from the session.
484bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown         * @param extras Optional parameters for the event, may be null.
48501fe661ae5da3739215d93922412df4b24c859a2RoboErik         */
486bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        public void onSessionEvent(@NonNull String event, @Nullable Bundle extras) {
48701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
48801fe661ae5da3739215d93922412df4b24c859a2RoboErik
48901fe661ae5da3739215d93922412df4b24c859a2RoboErik        /**
490c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Override to handle changes in playback state.
491c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
492c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @param state The new playback state of the session
493c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
494bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        public void onPlaybackStateChanged(@NonNull PlaybackState state) {
495c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
496c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
497c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
498c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Override to handle changes to the current metadata.
499c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
500bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown         * @param metadata The current metadata for the session or null if none.
501c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @see MediaMetadata
502c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
503bf58d9b727f1007c7c620f622ac1d8003b1b211bJeff Brown        public void onMetadataChanged(@Nullable MediaMetadata metadata) {
504c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
50519c9518f6a817d53d5234de0020313cab6950b2fRoboErik
50619c9518f6a817d53d5234de0020313cab6950b2fRoboErik        /**
507f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Override to handle changes to tracks in the queue.
508f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
509f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param queue A list of tracks in the current play queue. It should include the currently
510f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *              playing track as well as previous and upcoming tracks if applicable.
511f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @see MediaSession.Track
512f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
513f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueChanged(@Nullable List<MediaSession.Track> queue) {
514f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
515f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
516f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
517f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Override to handle changes to the queue title.
518f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
519f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param title The title that should be displayed along with the play queue such as
520f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *              "Now Playing". May be null if there is no such title.
521f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
522f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueTitleChanged(@Nullable CharSequence title) {
523f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
524f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
525f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
526f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Override to handle changes to the {@link MediaSession} extras.
527f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
528f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param extras The extras that can include other information associated with the
529f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *               {@link MediaSession}.
530f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
531f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onExtrasChanged(@Nullable Bundle extras) {
532f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
533f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
534f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
53519c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * Override to handle changes to the volume info.
53619c9518f6a817d53d5234de0020313cab6950b2fRoboErik         *
53719c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * @param info The current volume info for this session.
53819c9518f6a817d53d5234de0020313cab6950b2fRoboErik         */
53919c9518f6a817d53d5234de0020313cab6950b2fRoboErik        public void onVolumeInfoChanged(VolumeInfo info) {
54019c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
541c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    }
542c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
543c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    /**
544c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * Interface for controlling media playback on a session. This allows an app
545c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     * to send media transport commands to the session.
546c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik     */
547c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik    public final class TransportControls {
548c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        private static final String TAG = "TransportController";
549c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
550c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        private TransportControls() {
551c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
552c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
553c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
554c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Request that the player start its playback at its current position.
555c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
556c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void play() {
557c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
558c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.play();
559c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
560c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling play.", e);
561c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
562c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
563c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
564c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
565f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Request that the player start playback for a specific {@link Uri}.
566f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
567f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param uri The uri of the requested media.
568f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param extras Optional extras that can include extra information about the media item
569f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *               to be played.
570f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
571f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void playUri(Uri uri, Bundle extras) {
572f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (uri == null) {
573f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                throw new IllegalArgumentException("You must specify a non-null Uri for playUri.");
574f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
575f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            try {
576f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                mSessionBinder.playUri(uri, extras);
577f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            } catch (RemoteException e) {
578f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                Log.wtf(TAG, "Error calling play(" + uri + ").", e);
579f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
580f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
581f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
582f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
583f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Request that the player start playback for a specific search query.
584f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         *
585f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param query The search query.
586f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * @param extras Optional extras that can include extra information about the query.
587f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
588f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void playFromSearch(String query, Bundle extras) {
589f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (TextUtils.isEmpty(query)) {
590f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                throw new IllegalArgumentException(
591f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                        "You must specify a non-empty search query for playFromSearch.");
592f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
593f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            try {
594f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                mSessionBinder.playFromSearch(query, extras);
595f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            } catch (RemoteException e) {
596f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                Log.wtf(TAG, "Error calling play(" + query + ").", e);
597f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
598f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
599f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
600f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
601f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * Play a track with a specific id in the play queue.
602f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         * If you specify an id that is not in the play queue, the behavior is undefined.
603f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal         */
604f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void skipToTrack(long id) {
605f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            try {
606f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                mSessionBinder.skipToTrack(id);
607f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            } catch (RemoteException e) {
608f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                Log.wtf(TAG, "Error calling skipToTrack(" + id + ").", e);
609f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
610f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
611f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
612f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        /**
613c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Request that the player pause its playback and stay at its current
614c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * position.
615c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
616c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void pause() {
617c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
618c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.pause();
619c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
620c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling pause.", e);
621c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
622c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
623c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
624c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
625c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Request that the player stop its playback; it may clear its state in
626c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * whatever way is appropriate.
627c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
628c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void stop() {
629c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
630c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.stop();
631c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
632c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling stop.", e);
633c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
634c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
635c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
636c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
637c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Move to a new location in the media stream.
638c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
639c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @param pos Position to move to, in milliseconds.
640c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
641c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void seekTo(long pos) {
642c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
643c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.seekTo(pos);
644c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
645c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling seekTo.", e);
646c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
647c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
648c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
649c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
650c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Start fast forwarding. If playback is already fast forwarding this
651c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * may increase the rate.
652c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
653c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void fastForward() {
654c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
655c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.fastForward();
656c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
657c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling fastForward.", e);
658c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
659c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
660c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
661c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
662c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Skip to the next item.
663c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
664c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void skipToNext() {
665c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
666c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.next();
667c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
668c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling next.", e);
669c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
670c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
671c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
672c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
673c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Start rewinding. If playback is already rewinding this may increase
674c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * the rate.
675c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
676c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void rewind() {
677c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
678c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.rewind();
679c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
680c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling rewind.", e);
681c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
682c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
683c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
684c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
685c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Skip to the previous item.
686c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
687c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void skipToPrevious() {
688c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
689c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.previous();
690c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
691c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling previous.", e);
692c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
693c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
694c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik
695c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        /**
696c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * Rate the current content. This will cause the rating to be set for
697c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * the current user. The Rating type must match the type returned by
698c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * {@link #getRatingType()}.
699c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         *
700c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         * @param rating The rating to set for the current content
701c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik         */
702c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        public void setRating(Rating rating) {
703c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            try {
704c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                mSessionBinder.rate(rating);
705c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            } catch (RemoteException e) {
706c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                Log.wtf(TAG, "Error calling rate.", e);
707c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik            }
708c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik        }
709f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal
710f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        /**
711f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * Send a custom action back for the {@link MediaSession} to perform.
712f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *
713f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param customAction The action to perform.
714f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param args Optional arguments to supply to the {@link MediaSession} for this
715f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *             custom action.
716f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         */
717f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        public void sendCustomAction(@NonNull PlaybackState.CustomAction customAction,
718f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                    @Nullable Bundle args) {
719f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            if (customAction == null) {
720f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                throw new IllegalArgumentException("CustomAction cannot be null.");
721f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            }
722f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            sendCustomAction(customAction.getAction(), args);
723f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        }
724f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal
725f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        /**
726f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * Send the id and args from a custom action back for the {@link MediaSession} to perform.
727f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *
728f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @see #sendCustomAction(PlaybackState.CustomAction action, Bundle args)
729f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param action The action identifier of the {@link PlaybackState.CustomAction} as
730f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *               specified by the {@link MediaSession}.
731f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         * @param args Optional arguments to supply to the {@link MediaSession} for this
732f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         *             custom action.
733f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal         */
734f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        public void sendCustomAction(@NonNull String action, @Nullable Bundle args) {
735f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            if (TextUtils.isEmpty(action)) {
736f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                throw new IllegalArgumentException("CustomAction cannot be null.");
737f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            }
738f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            try {
739f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                mSessionBinder.sendCustomAction(action, args);
740f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            } catch (RemoteException e) {
741f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal                Log.d(TAG, "Dead object in sendCustomAction.", e);
742f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal            }
743f364f944962c4ec66f5e5b33dafe8480f38f6db6Gabriel Peal        }
7448ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
74501fe661ae5da3739215d93922412df4b24c859a2RoboErik
746ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    /**
747ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     * Holds information about the way volume is handled for this session.
748ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik     */
749ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    public static final class VolumeInfo {
750ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mVolumeType;
751ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mVolumeControl;
752ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mMaxVolume;
753ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        private final int mCurrentVolume;
7549db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik        private final AudioAttributes mAudioAttrs;
755ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
756ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
757ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @hide
758ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
7599db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik        public VolumeInfo(int type, AudioAttributes attrs, int control, int max, int current) {
760ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mVolumeType = type;
7619db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik            mAudioAttrs = attrs;
762ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mVolumeControl = control;
763ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mMaxVolume = max;
764ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            mCurrentVolume = current;
765ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
766ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
767ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
768ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the type of volume handling, either local or remote. One of:
769ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <ul>
77019c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * <li>{@link MediaSession#PLAYBACK_TYPE_LOCAL}</li>
77119c9518f6a817d53d5234de0020313cab6950b2fRoboErik         * <li>{@link MediaSession#PLAYBACK_TYPE_REMOTE}</li>
772ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * </ul>
773ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
774ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The type of volume handling this session is using.
775ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
776ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getVolumeType() {
777ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mVolumeType;
778ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
779ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
780ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
7819db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * Get the audio attributes for this session. The attributes will affect
7829db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * volume handling for the session. When the volume type is
7839db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * {@link MediaSession#PLAYBACK_TYPE_REMOTE} these may be ignored by the
7849db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * remote volume handler.
785ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
7869db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik         * @return The attributes for this session.
787ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
7889db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik        public AudioAttributes getAudioAttributes() {
7899db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik            return mAudioAttrs;
790ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
791ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
792ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
793ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the type of volume control that can be used. One of:
794ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <ul>
795ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
796ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
797ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
798ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * </ul>
799ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
800ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The type of volume control that may be used with this
801ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *         session.
802ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
803ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getVolumeControl() {
804ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mVolumeControl;
805ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
806ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
807ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
808ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the maximum volume that may be set for this session.
809ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
810ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The maximum allowed volume where this session is playing.
811ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
812ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getMaxVolume() {
813ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mMaxVolume;
814ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
815ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
816ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        /**
817ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * Get the current volume for this session.
818ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         *
819ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         * @return The current volume where this session is playing.
820ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik         */
821ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        public int getCurrentVolume() {
822ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik            return mCurrentVolume;
823ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        }
824ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik    }
825ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik
82607c7077c54717dbbf2c401ea32d00fa6df6d77c6RoboErik    private final static class CallbackStub extends ISessionControllerCallback.Stub {
82742ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        private final WeakReference<MediaController> mController;
82801fe661ae5da3739215d93922412df4b24c859a2RoboErik
82942ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        public CallbackStub(MediaController controller) {
83042ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            mController = new WeakReference<MediaController>(controller);
83101fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
83201fe661ae5da3739215d93922412df4b24c859a2RoboErik
83301fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
8348ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onEvent(String event, Bundle extras) {
83542ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            MediaController controller = mController.get();
8368ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
837c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                controller.postMessage(MSG_EVENT, event, extras);
83801fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
83901fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
84001fe661ae5da3739215d93922412df4b24c859a2RoboErik
84101fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
8428ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onPlaybackStateChanged(PlaybackState state) {
84342ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            MediaController controller = mController.get();
8448ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
845c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null);
84601fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
84701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
84801fe661ae5da3739215d93922412df4b24c859a2RoboErik
84901fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
8508ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onMetadataChanged(MediaMetadata metadata) {
85142ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik            MediaController controller = mController.get();
8528ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
853c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                controller.postMessage(MSG_UPDATE_METADATA, metadata, null);
85401fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
85501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
85601fe661ae5da3739215d93922412df4b24c859a2RoboErik
85719c9518f6a817d53d5234de0020313cab6950b2fRoboErik        @Override
858f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueChanged(ParceledListSlice parceledQueue) {
859f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            List<MediaSession.Track> queue = parceledQueue.getList();
860f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            MediaController controller = mController.get();
861f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (controller != null) {
862f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
863f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
864f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
865f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
866f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        @Override
867f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onQueueTitleChanged(CharSequence title) {
868f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            MediaController controller = mController.get();
869f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (controller != null) {
870f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                controller.postMessage(MSG_UPDATE_QUEUE_TITLE, title, null);
871f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
872f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
873f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
874f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        @Override
875f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        public void onExtrasChanged(Bundle extras) {
876f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            MediaController controller = mController.get();
877f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            if (controller != null) {
878f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                controller.postMessage(MSG_UPDATE_EXTRAS, extras, null);
879f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal            }
880f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        }
881f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal
882f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal        @Override
88319c9518f6a817d53d5234de0020313cab6950b2fRoboErik        public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
88419c9518f6a817d53d5234de0020313cab6950b2fRoboErik            MediaController controller = mController.get();
88519c9518f6a817d53d5234de0020313cab6950b2fRoboErik            if (controller != null) {
8869db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik                VolumeInfo info = new VolumeInfo(pvi.volumeType, pvi.audioAttrs, pvi.controlType,
88719c9518f6a817d53d5234de0020313cab6950b2fRoboErik                        pvi.maxVolume, pvi.currentVolume);
88819c9518f6a817d53d5234de0020313cab6950b2fRoboErik                controller.postMessage(MSG_UPDATE_VOLUME, info, null);
88919c9518f6a817d53d5234de0020313cab6950b2fRoboErik            }
89019c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
89119c9518f6a817d53d5234de0020313cab6950b2fRoboErik
89201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
89301fe661ae5da3739215d93922412df4b24c859a2RoboErik
89401fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final static class MessageHandler extends Handler {
89542ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        private final MediaController.Callback mCallback;
89601fe661ae5da3739215d93922412df4b24c859a2RoboErik
89742ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErik        public MessageHandler(Looper looper, MediaController.Callback cb) {
8988ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            super(looper, null, true);
8998ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            mCallback = cb;
90001fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
90101fe661ae5da3739215d93922412df4b24c859a2RoboErik
90201fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
90301fe661ae5da3739215d93922412df4b24c859a2RoboErik        public void handleMessage(Message msg) {
90401fe661ae5da3739215d93922412df4b24c859a2RoboErik            switch (msg.what) {
9058ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                case MSG_EVENT:
90679fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik                    mCallback.onSessionEvent((String) msg.obj, msg.getData());
90701fe661ae5da3739215d93922412df4b24c859a2RoboErik                    break;
908c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                case MSG_UPDATE_PLAYBACK_STATE:
909c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
910c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    break;
911c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                case MSG_UPDATE_METADATA:
912c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    mCallback.onMetadataChanged((MediaMetadata) msg.obj);
913c47fa84b0a6bda48c38ba8822481ce613bafd019RoboErik                    break;
914f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                case MSG_UPDATE_QUEUE:
915f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    mCallback.onQueueChanged((List<MediaSession.Track>) msg.obj);
916f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    break;
917f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                case MSG_UPDATE_QUEUE_TITLE:
918f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    mCallback.onQueueTitleChanged((CharSequence) msg.obj);
919f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    break;
920f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                case MSG_UPDATE_EXTRAS:
921f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    mCallback.onExtrasChanged((Bundle) msg.obj);
922f0593bc17b61c872ae2d7705fb598c5e5056e679Gabriel Peal                    break;
92319c9518f6a817d53d5234de0020313cab6950b2fRoboErik                case MSG_UPDATE_VOLUME:
92419c9518f6a817d53d5234de0020313cab6950b2fRoboErik                    mCallback.onVolumeInfoChanged((VolumeInfo) msg.obj);
92519c9518f6a817d53d5234de0020313cab6950b2fRoboErik                    break;
92601fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
92701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
9288ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
9298ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void post(int what, Object obj, Bundle data) {
9308ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            obtainMessage(what, obj).sendToTarget();
9318ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
93201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
93301fe661ae5da3739215d93922412df4b24c859a2RoboErik
93401fe661ae5da3739215d93922412df4b24c859a2RoboErik}
935