MediaController.java revision 8ae0f34db936a649ddaf9cdd086c224f6514efeb
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
1901fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Bundle;
2001fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Handler;
2101fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Looper;
2201fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.Message;
2301fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.os.RemoteException;
248ae0f34db936a649ddaf9cdd086c224f6514efebRoboErikimport android.os.ResultReceiver;
2501fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.text.TextUtils;
2601fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.util.Log;
2701fe661ae5da3739215d93922412df4b24c859a2RoboErikimport android.view.KeyEvent;
2801fe661ae5da3739215d93922412df4b24c859a2RoboErik
298ae0f34db936a649ddaf9cdd086c224f6514efebRoboErikimport java.lang.ref.WeakReference;
3001fe661ae5da3739215d93922412df4b24c859a2RoboErikimport java.util.ArrayList;
3101fe661ae5da3739215d93922412df4b24c859a2RoboErik
3201fe661ae5da3739215d93922412df4b24c859a2RoboErik/**
3301fe661ae5da3739215d93922412df4b24c859a2RoboErik * Allows an app to interact with an ongoing media session. Media buttons and
3401fe661ae5da3739215d93922412df4b24c859a2RoboErik * other commands can be sent to the session. A callback may be registered to
3501fe661ae5da3739215d93922412df4b24c859a2RoboErik * receive updates from the session, such as metadata and play state changes.
3601fe661ae5da3739215d93922412df4b24c859a2RoboErik * <p>
3701fe661ae5da3739215d93922412df4b24c859a2RoboErik * A MediaController can be created through {@link MediaSessionManager} if you
3801fe661ae5da3739215d93922412df4b24c859a2RoboErik * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
3901fe661ae5da3739215d93922412df4b24c859a2RoboErik * you have a {@link MediaSessionToken} from the session owner.
4001fe661ae5da3739215d93922412df4b24c859a2RoboErik * <p>
4101fe661ae5da3739215d93922412df4b24c859a2RoboErik * MediaController objects are thread-safe.
4201fe661ae5da3739215d93922412df4b24c859a2RoboErik */
4301fe661ae5da3739215d93922412df4b24c859a2RoboErikpublic final class MediaController {
4401fe661ae5da3739215d93922412df4b24c859a2RoboErik    private static final String TAG = "MediaController";
4501fe661ae5da3739215d93922412df4b24c859a2RoboErik
468ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private static final int MSG_EVENT = 1;
4701fe661ae5da3739215d93922412df4b24c859a2RoboErik    private static final int MESSAGE_PLAYBACK_STATE = 2;
4801fe661ae5da3739215d93922412df4b24c859a2RoboErik    private static final int MESSAGE_METADATA = 3;
498ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private static final int MSG_ROUTE = 4;
5001fe661ae5da3739215d93922412df4b24c859a2RoboErik
5101fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final IMediaController mSessionBinder;
5201fe661ae5da3739215d93922412df4b24c859a2RoboErik
538ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final CallbackStub mCbStub = new CallbackStub(this);
548ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
5501fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final Object mLock = new Object();
5601fe661ae5da3739215d93922412df4b24c859a2RoboErik
5701fe661ae5da3739215d93922412df4b24c859a2RoboErik    private boolean mCbRegistered = false;
5801fe661ae5da3739215d93922412df4b24c859a2RoboErik
598ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private TransportController mTransportController;
608ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
618ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private MediaController(IMediaController sessionBinder) {
628ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        mSessionBinder = sessionBinder;
638ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
648ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
6501fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
668ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @hide
6701fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
688ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    public static MediaController fromBinder(IMediaController sessionBinder) {
698ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        MediaController controller = new MediaController(sessionBinder);
708ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        try {
718ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            controller.mSessionBinder.registerCallbackListener(controller.mCbStub);
728ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller.mSessionBinder.isTransportControlEnabled()) {
738ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                controller.mTransportController = new TransportController(sessionBinder);
748ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
758ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        } catch (RemoteException e) {
768ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            Log.wtf(TAG, "MediaController created with expired token", e);
778ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            controller = null;
788ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
798ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return controller;
8001fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
8101fe661ae5da3739215d93922412df4b24c859a2RoboErik
8201fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
838ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Get a new MediaController for a MediaSessionToken. If successful the
848ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * controller returned will be connected to the session that generated the
858ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * token.
868ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     *
878ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param token The session token to use
888ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @return A controller for the session or null
8901fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
908ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    public static MediaController fromToken(MediaSessionToken token) {
918ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return fromBinder(token.getBinder());
9201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
9301fe661ae5da3739215d93922412df4b24c859a2RoboErik
9401fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
958ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Get a TransportController if the session supports it. If it is not
968ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * supported null will be returned.
9701fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
988ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @return A TransportController or null
9901fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
1008ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    public TransportController getTransportController() {
1018ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return mTransportController;
10201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
10301fe661ae5da3739215d93922412df4b24c859a2RoboErik
10401fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
10501fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Send the specified media button to the session. Only media keys can be
10601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * sent using this method.
10701fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
10801fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @param keycode The media button keycode, such as
1099524ed59a44b6850f3f708e50ef0708d2a462316RoboErik     *            {@link KeyEvent#KEYCODE_MEDIA_PLAY}.
11001fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
11101fe661ae5da3739215d93922412df4b24c859a2RoboErik    public void sendMediaButton(int keycode) {
11201fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (!KeyEvent.isMediaKey(keycode)) {
11301fe661ae5da3739215d93922412df4b24c859a2RoboErik            throw new IllegalArgumentException("May only send media buttons through "
11401fe661ae5da3739215d93922412df4b24c859a2RoboErik                    + "sendMediaButton");
11501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
11601fe661ae5da3739215d93922412df4b24c859a2RoboErik        // TODO do something better than key down/up events
11701fe661ae5da3739215d93922412df4b24c859a2RoboErik        KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keycode);
11801fe661ae5da3739215d93922412df4b24c859a2RoboErik        try {
11901fe661ae5da3739215d93922412df4b24c859a2RoboErik            mSessionBinder.sendMediaButton(event);
12001fe661ae5da3739215d93922412df4b24c859a2RoboErik        } catch (RemoteException e) {
12101fe661ae5da3739215d93922412df4b24c859a2RoboErik            Log.d(TAG, "Dead object in sendMediaButton", e);
12201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
12301fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
12401fe661ae5da3739215d93922412df4b24c859a2RoboErik
12501fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
12601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Adds a callback to receive updates from the Session. Updates will be
12701fe661ae5da3739215d93922412df4b24c859a2RoboErik     * posted on the caller's thread.
12801fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
12901fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @param cb The callback object, must not be null
13001fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
13101fe661ae5da3739215d93922412df4b24c859a2RoboErik    public void addCallback(Callback cb) {
13201fe661ae5da3739215d93922412df4b24c859a2RoboErik        addCallback(cb, null);
13301fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
13401fe661ae5da3739215d93922412df4b24c859a2RoboErik
13501fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
13601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Adds a callback to receive updates from the session. Updates will be
1378ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * posted on the specified handler's thread.
13801fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
13901fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @param cb Cannot be null.
1408ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param handler The handler to post updates on. If null the callers thread
14101fe661ae5da3739215d93922412df4b24c859a2RoboErik     *            will be used
14201fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
14301fe661ae5da3739215d93922412df4b24c859a2RoboErik    public void addCallback(Callback cb, Handler handler) {
14401fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (handler == null) {
14501fe661ae5da3739215d93922412df4b24c859a2RoboErik            handler = new Handler();
14601fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
14701fe661ae5da3739215d93922412df4b24c859a2RoboErik        synchronized (mLock) {
14801fe661ae5da3739215d93922412df4b24c859a2RoboErik            addCallbackLocked(cb, handler);
14901fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
15001fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
15101fe661ae5da3739215d93922412df4b24c859a2RoboErik
15201fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
15301fe661ae5da3739215d93922412df4b24c859a2RoboErik     * Stop receiving updates on the specified callback. If an update has
15401fe661ae5da3739215d93922412df4b24c859a2RoboErik     * already been posted you may still receive it after calling this method.
15501fe661ae5da3739215d93922412df4b24c859a2RoboErik     *
15601fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @param cb The callback to remove
15701fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
15801fe661ae5da3739215d93922412df4b24c859a2RoboErik    public void removeCallback(Callback cb) {
15901fe661ae5da3739215d93922412df4b24c859a2RoboErik        synchronized (mLock) {
16001fe661ae5da3739215d93922412df4b24c859a2RoboErik            removeCallbackLocked(cb);
16101fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
16201fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
16301fe661ae5da3739215d93922412df4b24c859a2RoboErik
1648ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    /**
1658ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Sends a generic command to the session. It is up to the session creator
1668ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * to decide what commands and parameters they will support. As such,
1678ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * commands should only be sent to sessions that the controller owns.
1688ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     *
1698ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param command The command to send
1708ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param params Any parameters to include with the command
1718ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * @param cb The callback to receive the result on
1728ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     */
1738ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1748ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (TextUtils.isEmpty(command)) {
1758ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            throw new IllegalArgumentException("command cannot be null or empty");
1768ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
1778ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        try {
1788ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            mSessionBinder.sendCommand(command, params, cb);
1798ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        } catch (RemoteException e) {
1808ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            Log.d(TAG, "Dead object in sendCommand.", e);
1818ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
1828ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
1838ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
18401fe661ae5da3739215d93922412df4b24c859a2RoboErik    /*
18501fe661ae5da3739215d93922412df4b24c859a2RoboErik     * @hide
18601fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
18701fe661ae5da3739215d93922412df4b24c859a2RoboErik    IMediaController getSessionBinder() {
18801fe661ae5da3739215d93922412df4b24c859a2RoboErik        return mSessionBinder;
18901fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
19001fe661ae5da3739215d93922412df4b24c859a2RoboErik
19101fe661ae5da3739215d93922412df4b24c859a2RoboErik    private void addCallbackLocked(Callback cb, Handler handler) {
19201fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (cb == null) {
19301fe661ae5da3739215d93922412df4b24c859a2RoboErik            throw new IllegalArgumentException("Callback cannot be null");
19401fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
19501fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (handler == null) {
19601fe661ae5da3739215d93922412df4b24c859a2RoboErik            throw new IllegalArgumentException("Handler cannot be null");
19701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
1988ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (getHandlerForCallbackLocked(cb) != null) {
19901fe661ae5da3739215d93922412df4b24c859a2RoboErik            Log.w(TAG, "Callback is already added, ignoring");
20001fe661ae5da3739215d93922412df4b24c859a2RoboErik            return;
20101fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
2028ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        MessageHandler holder = new MessageHandler(handler.getLooper(), cb);
2038ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        mCallbacks.add(holder);
20401fe661ae5da3739215d93922412df4b24c859a2RoboErik
20501fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (!mCbRegistered) {
20601fe661ae5da3739215d93922412df4b24c859a2RoboErik            try {
20701fe661ae5da3739215d93922412df4b24c859a2RoboErik                mSessionBinder.registerCallbackListener(mCbStub);
20801fe661ae5da3739215d93922412df4b24c859a2RoboErik                mCbRegistered = true;
20901fe661ae5da3739215d93922412df4b24c859a2RoboErik            } catch (RemoteException e) {
21001fe661ae5da3739215d93922412df4b24c859a2RoboErik                Log.d(TAG, "Dead object in registerCallback", e);
21101fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
21201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
21301fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
21401fe661ae5da3739215d93922412df4b24c859a2RoboErik
2158ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private boolean removeCallbackLocked(Callback cb) {
21601fe661ae5da3739215d93922412df4b24c859a2RoboErik        if (cb == null) {
21701fe661ae5da3739215d93922412df4b24c859a2RoboErik            throw new IllegalArgumentException("Callback cannot be null");
21801fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
2198ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
2208ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MessageHandler handler = mCallbacks.get(i);
2218ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (cb == handler.mCallback) {
2228ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                mCallbacks.remove(i);
2238ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                return true;
22401fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
22501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
2268ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return false;
22701fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
22801fe661ae5da3739215d93922412df4b24c859a2RoboErik
2298ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private MessageHandler getHandlerForCallbackLocked(Callback cb) {
2308ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        if (cb == null) {
2318ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            throw new IllegalArgumentException("Callback cannot be null");
23201fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
2338ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
2348ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MessageHandler handler = mCallbacks.get(i);
2358ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (cb == handler.mCallback) {
2368ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                return handler;
2378ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
23801fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
2398ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        return null;
24001fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
24101fe661ae5da3739215d93922412df4b24c859a2RoboErik
2428ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private void postEvent(String event, Bundle extras) {
2438ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        synchronized (mLock) {
2448ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
2458ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                mCallbacks.get(i).post(MSG_EVENT, event, extras);
2468ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
24701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
24801fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
24901fe661ae5da3739215d93922412df4b24c859a2RoboErik
2508ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private void postRouteChanged(Bundle routeDescriptor) {
2518ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        synchronized (mLock) {
2528ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
2538ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                mCallbacks.get(i).post(MSG_ROUTE, null, routeDescriptor);
2548ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            }
25501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
25601fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
25701fe661ae5da3739215d93922412df4b24c859a2RoboErik
25801fe661ae5da3739215d93922412df4b24c859a2RoboErik    /**
2598ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * Callback for receiving updates on from the session. A Callback can be
2608ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik     * registered using {@link #addCallback}
26101fe661ae5da3739215d93922412df4b24c859a2RoboErik     */
26201fe661ae5da3739215d93922412df4b24c859a2RoboErik    public static abstract class Callback {
26301fe661ae5da3739215d93922412df4b24c859a2RoboErik        /**
2648ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * Override to handle custom events sent by the session owner without a
2658ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * specified interface. Controllers should only handle these for
2668ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik         * sessions they own.
26701fe661ae5da3739215d93922412df4b24c859a2RoboErik         *
26801fe661ae5da3739215d93922412df4b24c859a2RoboErik         * @param event
26901fe661ae5da3739215d93922412df4b24c859a2RoboErik         */
27001fe661ae5da3739215d93922412df4b24c859a2RoboErik        public void onEvent(String event, Bundle extras) {
27101fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
27201fe661ae5da3739215d93922412df4b24c859a2RoboErik
27301fe661ae5da3739215d93922412df4b24c859a2RoboErik        /**
27401fe661ae5da3739215d93922412df4b24c859a2RoboErik         * Override to handle route changes for this session.
27501fe661ae5da3739215d93922412df4b24c859a2RoboErik         *
27601fe661ae5da3739215d93922412df4b24c859a2RoboErik         * @param route
27701fe661ae5da3739215d93922412df4b24c859a2RoboErik         */
27801fe661ae5da3739215d93922412df4b24c859a2RoboErik        public void onRouteChanged(Bundle route) {
27901fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
2808ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    }
28101fe661ae5da3739215d93922412df4b24c859a2RoboErik
2828ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik    private final static class CallbackStub extends IMediaControllerCallback.Stub {
2838ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        private final WeakReference<MediaController> mController;
28401fe661ae5da3739215d93922412df4b24c859a2RoboErik
2858ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public CallbackStub(MediaController controller) {
2868ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            mController = new WeakReference<MediaController>(controller);
28701fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
28801fe661ae5da3739215d93922412df4b24c859a2RoboErik
28901fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
2908ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onEvent(String event, Bundle extras) {
2918ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MediaController controller = mController.get();
2928ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
2938ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                controller.postEvent(event, extras);
29401fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
29501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
29601fe661ae5da3739215d93922412df4b24c859a2RoboErik
29701fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
2988ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onRouteChanged(Bundle mediaRouteDescriptor) {
2998ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MediaController controller = mController.get();
3008ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
3018ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                controller.postRouteChanged(mediaRouteDescriptor);
30201fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
30301fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
30401fe661ae5da3739215d93922412df4b24c859a2RoboErik
30501fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
3068ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onPlaybackStateChanged(PlaybackState state) {
3078ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MediaController controller = mController.get();
3088ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
3098ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                TransportController tc = controller.getTransportController();
3108ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                if (tc != null) {
3118ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                    tc.postPlaybackStateChanged(state);
3128ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                }
31301fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
31401fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
31501fe661ae5da3739215d93922412df4b24c859a2RoboErik
31601fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
3178ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void onMetadataChanged(MediaMetadata metadata) {
3188ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            MediaController controller = mController.get();
3198ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            if (controller != null) {
3208ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                TransportController tc = controller.getTransportController();
3218ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                if (tc != null) {
3228ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                    tc.postMetadataChanged(metadata);
3238ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                }
32401fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
32501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
32601fe661ae5da3739215d93922412df4b24c859a2RoboErik
32701fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
32801fe661ae5da3739215d93922412df4b24c859a2RoboErik
32901fe661ae5da3739215d93922412df4b24c859a2RoboErik    private final static class MessageHandler extends Handler {
3308ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        private final MediaController.Callback mCallback;
33101fe661ae5da3739215d93922412df4b24c859a2RoboErik
33201fe661ae5da3739215d93922412df4b24c859a2RoboErik        public MessageHandler(Looper looper, MediaController.Callback cb) {
3338ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            super(looper, null, true);
3348ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            mCallback = cb;
33501fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
33601fe661ae5da3739215d93922412df4b24c859a2RoboErik
33701fe661ae5da3739215d93922412df4b24c859a2RoboErik        @Override
33801fe661ae5da3739215d93922412df4b24c859a2RoboErik        public void handleMessage(Message msg) {
33901fe661ae5da3739215d93922412df4b24c859a2RoboErik            switch (msg.what) {
3408ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                case MSG_EVENT:
3418ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                    mCallback.onEvent((String) msg.obj, msg.getData());
34201fe661ae5da3739215d93922412df4b24c859a2RoboErik                    break;
3438ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                case MSG_ROUTE:
3448ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik                    mCallback.onRouteChanged(msg.getData());
34501fe661ae5da3739215d93922412df4b24c859a2RoboErik            }
34601fe661ae5da3739215d93922412df4b24c859a2RoboErik        }
3478ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik
3488ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        public void post(int what, Object obj, Bundle data) {
3498ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik            obtainMessage(what, obj).sendToTarget();
3508ae0f34db936a649ddaf9cdd086c224f6514efebRoboErik        }
35101fe661ae5da3739215d93922412df4b24c859a2RoboErik    }
35201fe661ae5da3739215d93922412df4b24c859a2RoboErik
35301fe661ae5da3739215d93922412df4b24c859a2RoboErik}
354