19a1de308cea2d160778fd977825f10a07b49d738Adam Powell/*
29a1de308cea2d160778fd977825f10a07b49d738Adam Powell * Copyright (C) 2012 The Android Open Source Project
39a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
49a1de308cea2d160778fd977825f10a07b49d738Adam Powell * Licensed under the Apache License, Version 2.0 (the "License");
59a1de308cea2d160778fd977825f10a07b49d738Adam Powell * you may not use this file except in compliance with the License.
69a1de308cea2d160778fd977825f10a07b49d738Adam Powell * You may obtain a copy of the License at
79a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
89a1de308cea2d160778fd977825f10a07b49d738Adam Powell *      http://www.apache.org/licenses/LICENSE-2.0
99a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
109a1de308cea2d160778fd977825f10a07b49d738Adam Powell * Unless required by applicable law or agreed to in writing, software
119a1de308cea2d160778fd977825f10a07b49d738Adam Powell * distributed under the License is distributed on an "AS IS" BASIS,
129a1de308cea2d160778fd977825f10a07b49d738Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139a1de308cea2d160778fd977825f10a07b49d738Adam Powell * See the License for the specific language governing permissions and
149a1de308cea2d160778fd977825f10a07b49d738Adam Powell * limitations under the License.
159a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
169a1de308cea2d160778fd977825f10a07b49d738Adam Powell
179a1de308cea2d160778fd977825f10a07b49d738Adam Powellpackage android.media;
189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
198e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.BroadcastReceiver;
209a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.content.Context;
218e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.Intent;
228e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.IntentFilter;
23b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackbornimport android.content.res.Resources;
24ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powellimport android.graphics.drawable.Drawable;
259a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.os.Handler;
26632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.IBinder;
27632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.RemoteException;
28632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.ServiceManager;
29632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.text.TextUtils;
309a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.util.Log;
319a1de308cea2d160778fd977825f10a07b49d738Adam Powell
329a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.ArrayList;
339a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.HashMap;
34d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powellimport java.util.List;
3539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powellimport java.util.concurrent.CopyOnWriteArrayList;
369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
379a1de308cea2d160778fd977825f10a07b49d738Adam Powell/**
389a1de308cea2d160778fd977825f10a07b49d738Adam Powell * MediaRouter allows applications to control the routing of media channels
399a1de308cea2d160778fd977825f10a07b49d738Adam Powell * and streams from the current device to external speakers and destination devices.
409a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
41b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
42b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
43b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.MEDIA_ROUTER_SERVICE}.
44b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn *
45b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>The media router API is not thread-safe; all interactions with it must be
46b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * done from the main thread of the process.</p>
479a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
489a1de308cea2d160778fd977825f10a07b49d738Adam Powellpublic class MediaRouter {
499a1de308cea2d160778fd977825f10a07b49d738Adam Powell    private static final String TAG = "MediaRouter";
509a1de308cea2d160778fd977825f10a07b49d738Adam Powell
51b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static class Static {
52b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Resources mResources;
53632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioService mAudioService;
54b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Handler mHandler;
5539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
5639d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell                new CopyOnWriteArrayList<CallbackInfo>();
57b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
58b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
59b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
60b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
61b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final RouteCategory mSystemCategory;
62632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
63632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo();
64b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
65b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mDefaultAudio;
66b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mBluetoothA2dpRoute;
67b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
68b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mSelectedRoute;
699a1de308cea2d160778fd977825f10a07b49d738Adam Powell
70632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() {
71632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
72632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mHandler.post(new Runnable() {
73632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    @Override public void run() {
74632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        updateRoutes(newRoutes);
75632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
76632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                });
77632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
78632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        };
79632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
80b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        Static(Context appContext) {
81b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mResources = Resources.getSystem();
82b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mHandler = new Handler(appContext.getMainLooper());
839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
84632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
85632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            mAudioService = IAudioService.Stub.asInterface(b);
869a1de308cea2d160778fd977825f10a07b49d738Adam Powell
87dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            mSystemCategory = new RouteCategory(
88dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                    com.android.internal.R.string.default_audio_route_category_name,
89b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                    ROUTE_TYPE_LIVE_AUDIO, false);
90b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        }
91b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
92b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        // Called after sStatic is initialized
938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        void startMonitoringRoutes(Context appContext) {
94b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mDefaultAudio = new RouteInfo(mSystemCategory);
950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
96b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
97b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            addRoute(mDefaultAudio);
98632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            appContext.registerReceiver(new VolumeChangeReceiver(),
1008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
1018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
102632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            AudioRoutesInfo newRoutes = null;
103632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            try {
104632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver);
105632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            } catch (RemoteException e) {
106632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
107632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            if (newRoutes != null) {
108632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                updateRoutes(newRoutes);
109632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
110632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        }
111632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
112632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        void updateRoutes(AudioRoutesInfo newRoutes) {
113632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            if (newRoutes.mMainType != mCurRoutesInfo.mMainType) {
114632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mCurRoutesInfo.mMainType = newRoutes.mMainType;
115632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                int name;
116632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
117632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
118632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_headphones;
119632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
120632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
121632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
122632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_hdmi;
123632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else {
124632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name;
125632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
126632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                sStatic.mDefaultAudio.mNameResId = name;
127632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                dispatchRouteChanged(sStatic.mDefaultAudio);
128632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
129bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
130bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            boolean a2dpEnabled;
131bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            try {
132bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                a2dpEnabled = mAudioService.isBluetoothA2dpOn();
133bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            } catch (RemoteException e) {
134bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                Log.e(TAG, "Error querying Bluetooth A2DP state", e);
135bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                a2dpEnabled = false;
136bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
137bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
138632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) {
139632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
140632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                if (mCurRoutesInfo.mBluetoothName != null) {
141632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    if (sStatic.mBluetoothA2dpRoute == null) {
142632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
143632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mName = mCurRoutesInfo.mBluetoothName;
144632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
145632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        sStatic.mBluetoothA2dpRoute = info;
146632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        addRoute(sStatic.mBluetoothA2dpRoute);
147632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    } else {
148632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName;
149632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
150632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
151632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if (sStatic.mBluetoothA2dpRoute != null) {
152632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    removeRoute(sStatic.mBluetoothA2dpRoute);
153632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    sStatic.mBluetoothA2dpRoute = null;
154632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
155632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
156bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
157bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            if (mBluetoothA2dpRoute != null) {
158bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                if (mCurRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER &&
159bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                        mSelectedRoute == mBluetoothA2dpRoute) {
160bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudio);
161bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                } else if (mCurRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER &&
162bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                        mSelectedRoute == mDefaultAudio && a2dpEnabled) {
163bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
164bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                }
165bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
166b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        }
167b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
1689a1de308cea2d160778fd977825f10a07b49d738Adam Powell
169b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static Static sStatic;
1709a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
1729a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for live audio.
1739a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
1749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A device that supports live audio routing will allow the media audio stream
1759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to be routed to supported destinations. This can include internal speakers or
1769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * audio jacks on the device itself, A2DP devices, and more.</p>
1779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
1789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Once initiated this routing is transparent to the application. All audio
1799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * played on the media stream will be routed to the selected destination.</p>
1809a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
1829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
1849a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for application-specific usage.
1859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
1869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Unlike other media route types, user routes are managed by the application.
1879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * The MediaRouter will manage and dispatch events for user routes, but the application
1889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * is expected to interpret the meaning of these events and perform the requested
1899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * routing tasks.</p>
1909a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1919a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_USER = 0x00800000;
1929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    // Maps application contexts
1949a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
1959a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1969a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static String typesToString(int types) {
1979a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final StringBuilder result = new StringBuilder();
1989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
1999a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_LIVE_AUDIO ");
2009a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_USER) != 0) {
2029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_USER ");
2039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return result.toString();
2059a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2069a1de308cea2d160778fd977825f10a07b49d738Adam Powell
207b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    /** @hide */
208b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public MediaRouter(Context context) {
209b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        synchronized (Static.class) {
210b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic == null) {
2118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final Context appContext = context.getApplicationContext();
2128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic = new Static(appContext);
2138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic.startMonitoringRoutes(appContext);
214b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            }
2159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2169a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2179a1de308cea2d160778fd977825f10a07b49d738Adam Powell
218690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
219690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @hide for use by framework routing UI
220690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
221690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSystemAudioRoute() {
222b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mDefaultAudio;
223690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
224690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
225690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
2264599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     * @hide for use by framework routing UI
2274599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     */
2284599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    public RouteCategory getSystemAudioCategory() {
2294599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        return sStatic.mSystemCategory;
2304599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    }
2314599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
2324599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    /**
233690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Return the currently selected route for the given types
234690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
235690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @param type route types
236690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @return the selected route
237690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
238690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSelectedRoute(int type) {
239b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mSelectedRoute;
240690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
241690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
2429a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
2439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add a callback to listen to events about specific kinds of media routes.
2449a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * If the specified callback is already registered, its registration will be updated for any
2459a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * additional route types specified.
2469a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2479a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param types Types of routes this callback is interested in
2489a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to add
2499a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addCallback(int types, Callback cb) {
251b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
2529a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
253b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final CallbackInfo info = sStatic.mCallbacks.get(i);
2549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (info.cb == cb) {
255dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell                info.type |= types;
2569a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
2579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
2589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
259b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mCallbacks.add(new CallbackInfo(cb, types, this));
2609a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2619a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2629a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
2639a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove the specified callback. It will no longer receive events about media routing.
2649a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2659a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to remove
2669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2679a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeCallback(Callback cb) {
268b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
2699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
270b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic.mCallbacks.get(i).cb == cb) {
271b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCallbacks.remove(i);
2729a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
2739a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
2749a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
2769a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2779a1de308cea2d160778fd977825f10a07b49d738Adam Powell
278d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    /**
279d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * Select the specified route to use for output of the given media types.
280d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *
281d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param types type flags indicating which types this route should be used for.
282d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *              The route must support at least a subset.
283d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param route Route to select
284d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     */
2859a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void selectRoute(int types, RouteInfo route) {
2860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        // Applications shouldn't programmatically change anything but user routes.
2870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        types &= ROUTE_TYPE_USER;
2880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        selectRouteStatic(types, route);
2890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
2900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
2910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
2920d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @hide internal use
2930d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
2940d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public void selectRouteInt(int types, RouteInfo route) {
295b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        selectRouteStatic(types, route);
296b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
297b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
298b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void selectRouteStatic(int types, RouteInfo route) {
299b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mSelectedRoute == route) return;
3000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        if ((route.getSupportedTypes() & types) == 0) {
3010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
3020d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(route.getSupportedTypes()) + " into route types " +
3030d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(types));
3044ee1f55ce0f4909a7430ab44563a81852f335071Adam Powell            return;
3050d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
3069a1de308cea2d160778fd977825f10a07b49d738Adam Powell
307dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
308dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
309dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                (route == btRoute || route == sStatic.mDefaultAudio)) {
310dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            try {
311dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
312dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            } catch (RemoteException e) {
313dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                Log.e(TAG, "Error changing Bluetooth A2DP state", e);
314dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            }
315dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        }
316dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell
317b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mSelectedRoute != null) {
3189a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
319b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(),
320b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                    sStatic.mSelectedRoute);
3219a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
322b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mSelectedRoute = route;
3239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (route != null) {
3249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
3259a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteSelected(types & route.getSupportedTypes(), route);
3269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3279a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3289a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3299a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3309a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add an app-specified route for media to the MediaRouter.
3319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
3329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3339a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to add
3349a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createUserRoute()
3359a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
3369a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3379a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addUserRoute(UserRouteInfo info) {
3389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        addRoute(info);
3399a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3409a1de308cea2d160778fd977825f10a07b49d738Adam Powell
341d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
342d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide Framework use only
343d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
344d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void addRouteInt(RouteInfo info) {
345d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        addRoute(info);
346d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
347d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
348b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void addRoute(RouteInfo info) {
3499a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory cat = info.getCategory();
350b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (!sStatic.mCategories.contains(cat)) {
351b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mCategories.add(cat);
3529a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
353b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final boolean onlyRoute = sStatic.mRoutes.isEmpty();
354d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        if (cat.isGroupable() && !(info instanceof RouteGroup)) {
3559a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // Enforce that any added route in a groupable category must be in a group.
3569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteGroup group = new RouteGroup(info.getCategory());
357dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell            group.mSupportedTypes = info.mSupportedTypes;
358b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(group);
359d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(group);
360b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            group.addRoute(info);
361d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
3629a1de308cea2d160778fd977825f10a07b49d738Adam Powell            info = group;
363d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        } else {
364b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(info);
365d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(info);
3669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
367d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
3689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (onlyRoute) {
369b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            selectRouteStatic(info.getSupportedTypes(), info);
3709a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove an app-specified route for media from the MediaRouter.
3759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to remove
3779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
3789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3799a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeUserRoute(UserRouteInfo info) {
3809a1de308cea2d160778fd977825f10a07b49d738Adam Powell        removeRoute(info);
3819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
383690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
384690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Remove all app-specified routes from the MediaRouter.
385690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
386690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @see #removeUserRoute(UserRouteInfo)
387690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
388690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public void clearUserRoutes() {
389b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        for (int i = 0; i < sStatic.mRoutes.size(); i++) {
390b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.get(i);
391d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // TODO Right now, RouteGroups only ever contain user routes.
392d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // The code below will need to change if this assumption does.
393d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
394690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                removeRouteAt(i);
395690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                i--;
396690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
397690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
398690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
399690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
400d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
401d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide internal use only
402d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
403d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void removeRouteInt(RouteInfo info) {
404d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        removeRoute(info);
405d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
406d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
407b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void removeRoute(RouteInfo info) {
408b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mRoutes.remove(info)) {
4099a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteCategory removingCat = info.getCategory();
410b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
4119a1de308cea2d160778fd977825f10a07b49d738Adam Powell            boolean found = false;
4129a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
413b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
4149a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (removingCat == cat) {
4159a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    found = true;
4169a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    break;
4179a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
4189a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
419d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
420d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
421d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
422d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
423d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
4249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!found) {
425b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
4269a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
4279a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteRemoved(info);
4289a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
4299a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4309a1de308cea2d160778fd977825f10a07b49d738Adam Powell
431690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    void removeRouteAt(int routeIndex) {
432b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
433b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
434690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            final RouteCategory removingCat = info.getCategory();
435b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
436690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            boolean found = false;
437690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            for (int i = 0; i < count; i++) {
438b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
439690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                if (removingCat == cat) {
440690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    found = true;
441690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    break;
442690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                }
443690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
444d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
445d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
446d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
447d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
448d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
449690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            if (!found) {
450b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
451690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
452690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            dispatchRouteRemoved(info);
453690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
454690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
455690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
4569a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4579a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteCategory categories} currently
4589a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * represented by routes known to this MediaRouter.
4599a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4609a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of unique categories represented by this MediaRouter's known routes
4619a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4629a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getCategoryCount() {
463b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.size();
4649a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4659a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4669a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4679a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the {@link MediaRouter.RouteCategory category} at the given index.
4689a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Valid indices are in the range [0-getCategoryCount).
4699a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4709a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index which category to return
4719a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the category at index
4729a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory getCategoryAt(int index) {
474b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.get(index);
4759a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4769a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4779a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteInfo routes} currently known
4799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to this MediaRouter.
4809a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4819a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of routes tracked by this router
4829a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getRouteCount() {
484b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
4859a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4869a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4879a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the route at the specified index.
4899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4909a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index index of the route to return
4919a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the route at index
4929a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteInfo getRouteAt(int index) {
494b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
495b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
496b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
497b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static int getRouteCountStatic() {
498b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
499b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
500b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
501b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static RouteInfo getRouteAtStatic(int index) {
502b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
5039a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5059a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5069a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new user route that may be modified and registered for use by the application.
5079a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5089a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param category The category the new route will belong to
5099a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return A new UserRouteInfo for use by the application
5109a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5119a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
5129a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
5139a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createRouteCategory(CharSequence)
5149a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5159a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public UserRouteInfo createUserRoute(RouteCategory category) {
5169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new UserRouteInfo(category);
5179a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5199a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5209a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new route category. Each route must belong to a category.
5219a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5229a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param name Name of the new category
5239a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
5249a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the new RouteCategory
5259a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5269a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
5279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
5289a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5290d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
5300d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
5310d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Create a new route category. Each route must belong to a category.
5320d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     *
5330d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param nameResId Resource ID of the name of the new category
5340d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
5350d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @return the new RouteCategory
5360d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
5370d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
5380d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
5390d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
5409a1de308cea2d160778fd977825f10a07b49d738Adam Powell
541b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void updateRoute(final RouteInfo info) {
5429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        dispatchRouteChanged(info);
5439a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5449a1de308cea2d160778fd977825f10a07b49d738Adam Powell
545b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteSelected(int type, RouteInfo info) {
54639d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5479a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
548b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteSelected(cbi.router, type, info);
5499a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5509a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5519a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5529a1de308cea2d160778fd977825f10a07b49d738Adam Powell
553b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUnselected(int type, RouteInfo info) {
55439d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5559a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
556b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUnselected(cbi.router, type, info);
5579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5609a1de308cea2d160778fd977825f10a07b49d738Adam Powell
561b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteChanged(RouteInfo info) {
56239d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5639a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
564b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteChanged(cbi.router, info);
5659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5679a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5689a1de308cea2d160778fd977825f10a07b49d738Adam Powell
569b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteAdded(RouteInfo info) {
57039d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5719a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
572b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteAdded(cbi.router, info);
5739a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5749a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5759a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5769a1de308cea2d160778fd977825f10a07b49d738Adam Powell
577b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteRemoved(RouteInfo info) {
57839d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5799a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
580b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteRemoved(cbi.router, info);
5819a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5829a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5849a1de308cea2d160778fd977825f10a07b49d738Adam Powell
585b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
58639d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
587d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
588b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteGrouped(cbi.router, info, group, index);
589d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
590d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
591d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    }
592d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
593b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
59439d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
595d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
596b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUngrouped(cbi.router, info, group);
5979a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5999a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void dispatchRouteVolumeChanged(RouteInfo info) {
6028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
6048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                cbi.cb.onRouteVolumeChanged(cbi.router, info);
6058e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
6068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
6078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
6088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void systemVolumeChanged(int newValue) {
6108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        final RouteInfo selectedRoute = sStatic.mSelectedRoute;
6118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == null) return;
6128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
6148e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                selectedRoute == sStatic.mDefaultAudio) {
6158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(selectedRoute);
6168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else if (sStatic.mBluetoothA2dpRoute != null) {
6178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            try {
6188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
6198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio);
6208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } catch (RemoteException e) {
6218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
6228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
6238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else {
6248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(sStatic.mDefaultAudio);
6258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
6268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
6278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6289a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
6299a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a media route.
6309a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
631b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteInfo {
6329a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
6330d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
6349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private CharSequence mStatus;
6359a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mSupportedTypes;
6369a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup mGroup;
6379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory mCategory;
638ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        Drawable mIcon;
6391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        // playback information
6401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackType = PLAYBACK_TYPE_LOCAL;
6411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
6421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
6431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
6441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackStream = AudioManager.STREAM_MUSIC;
6451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        VolumeCallbackInfo mVcb;
6469a1de308cea2d160778fd977825f10a07b49d738Adam Powell
647b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        private Object mTag;
648b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
6491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * The default playback type, "local", indicating the presentation of the media is happening
6511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * on the same device (e.g. a phone, a tablet) as where it is controlled from.
6521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
6531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_LOCAL = 0;
6551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * A playback type indicating the presentation of the media is happening on
6571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * a different device (i.e. the remote device) than where it is controlled from.
6581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
6591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_REMOTE = 1;
6611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is fixed, i.e. it cannot be
6631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * controlled from this object. An example of fixed playback volume is a remote player,
6641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
6651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * than attenuate at the source.
6661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setVolumeHandling(int)
6671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_FIXED = 0;
6691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is variable and can be controlled
6711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * from this object.
6721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_VARIABLE = 1;
6741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
6759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteInfo(RouteCategory category) {
6769a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mCategory = category;
6779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6789a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
6809a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly name of a media route. This is the string presented
6819a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * to users who may select this as the active route.
6829a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
6839a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
6840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
6850d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
6860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
6870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
6880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/resource selected name of this route.
6890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
6900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context used to resolve the correct configuration to load
6910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return The user-friendly name of the media route. This is the string presented
6920d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * to users who may select this as the active route.
6930d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
6940d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
6950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
6960d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
6970d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
6980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
6990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
7000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return mName = res.getText(mNameResId);
7010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
7029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
7039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7069a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly status for a media route. This may include a description
7079a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of the currently playing media, if available.
7089a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getStatus() {
7109a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mStatus;
7119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7129a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7139a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7149a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return A media type flag set describing which types this route supports.
7159a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
7179a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mSupportedTypes;
7189a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7199a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7209a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The group that this route belongs to.
7229a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteGroup getGroup() {
7249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroup;
7259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7269a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the category this route belongs to.
7299a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7309a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteCategory getCategory() {
7319a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mCategory;
7329a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7339a1de308cea2d160778fd977825f10a07b49d738Adam Powell
734ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
735ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Get the icon representing this route.
736ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * This icon will be used in picker UIs if available.
737ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
738ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @return the icon representing this route or null if no icon is available
739ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
740ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public Drawable getIconDrawable() {
741ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            return mIcon;
742ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
743ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
744b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
745b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * Set an application-specific tag object for this route.
746b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * The application may use this to store arbitrary data associated with the
747b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * route for internal tracking.
748b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
749b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * <p>Note that the lifespan of a route may be well past the lifespan of
750b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * an Activity or other Context; take care that objects you store here
751b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * will not keep more data in memory alive than you intend.</p>
752b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
753b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @param tag Arbitrary, app-specific data for this route to hold for later use
754b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
755b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public void setTag(Object tag) {
756b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            mTag = tag;
757130b4572d1f3df702e5b296a655d15a41f6d4c66Adam Powell            routeUpdated();
758b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
759b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
760b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
761b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @return The tag object previously set by the application
762b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @see #setTag(Object)
763b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
764b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public Object getTag() {
765b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            return mTag;
766b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
767b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
7681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the type of playback associated with this route
7701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackType(int)
7711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
7721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackType() {
7731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackType;
7741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
7751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
7761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the stream over which the playback associated with this route is performed
7781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackStream(int)
7791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
7801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackStream() {
7811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackStream;
7821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
7831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
7841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Return the current volume for this route. Depending on the route, this may only
7868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * be valid if the route is currently selected.
7878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
7881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the volume at which the playback associated with this route is performed
7891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolume(int)
7901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
7911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolume() {
7921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
7931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int vol = 0;
7941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
7951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream);
7961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
7971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
7981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
7991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return vol;
8001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
8011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolume;
8021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
8031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
8041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request a volume change for this route.
8078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param volume value between 0 and getVolumeMax
8088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
8098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
8108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
8118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
8128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
8138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
8148e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
8158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
8168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
8178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
8188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
8198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
8208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
8218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
8228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
8238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
8248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request an incremental volume update for this route.
8258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param direction Delta to apply to the current volume
8268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
8278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
8288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
8298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
8308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    final int volume =
8318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                            Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
8328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
8338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
8348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
8358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
8368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
8378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
8388e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
8398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
8408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
8418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
8428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
8438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
8441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the maximum volume at which the playback associated with this route is performed
8451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeMax(int)
8461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeMax() {
8481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
8491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int volMax = 0;
8501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
8511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    volMax = sStatic.mAudioService.getStreamMaxVolume(mPlaybackStream);
8521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
8531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
8541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
8551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return volMax;
8561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
8571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolumeMax;
8581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
8591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
8601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return how volume is handling on the route
8631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeHandling(int)
8641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeHandling() {
8661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mVolumeHandling;
8671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
8681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void setStatusInt(CharSequence status) {
8709a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!status.equals(mStatus)) {
8719a1de308cea2d160778fd977825f10a07b49d738Adam Powell                mStatus = status;
8729a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (mGroup != null) {
8739a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    mGroup.memberStatusChanged(this, status);
8749a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
8759a1de308cea2d160778fd977825f10a07b49d738Adam Powell                routeUpdated();
8769a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
8779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
8789a1de308cea2d160778fd977825f10a07b49d738Adam Powell
8791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
8801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
8811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                sStatic.mHandler.post(new Runnable() {
8821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    @Override
8831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    public void run() {
8841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                      //Log.d(TAG, "dispatchRemoteVolumeUpdate dir=" + direction + " val=" + value);
8851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        if (mVcb != null) {
8861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            if (direction != 0) {
8871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
8881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            } else {
8891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeSetRequest(mVcb.route, value);
8901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            }
8911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        }
8921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    }
8931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                });
8941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
8951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        };
8961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8979a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void routeUpdated() {
8989a1de308cea2d160778fd977825f10a07b49d738Adam Powell            updateRoute(this);
8999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
9029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
903d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            String supportedTypes = typesToString(getSupportedTypes());
904d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() +
905d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " category=" + getCategory() +
9069a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    " supportedTypes=" + supportedTypes + "}";
9079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
9099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9109a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
9119a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that the application may define and modify.
9128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
9138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
9149a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
9159a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter.RouteInfo
9169a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
917b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class UserRouteInfo extends RouteInfo {
918ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        RemoteControlClient mRcc;
9199a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9209a1de308cea2d160778fd977825f10a07b49d738Adam Powell        UserRouteInfo(RouteCategory category) {
9219a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
9229a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mSupportedTypes = ROUTE_TYPE_USER;
9238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = PLAYBACK_TYPE_REMOTE;
9248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
9259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9269a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the user-visible name of this route.
9299a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param name Name to display to the user to describe this route
9309a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9319a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setName(CharSequence name) {
9329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
9339a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
9349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9350d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
9360d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
9370d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Set the user-visible name of this route.
9380d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param resId Resource ID of the name to display to the user to describe this route
9390d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
9400d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public void setName(int resId) {
9410d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = resId;
9420d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mName = null;
9430d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            routeUpdated();
9440d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9459a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9469a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9479a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the current user-visible status for this route.
9489a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param status Status to display to the user to describe what the endpoint
9499a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of this route is currently doing
9509a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9519a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setStatus(CharSequence status) {
9529a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
9539a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
954ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
955ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
956ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set the RemoteControlClient responsible for reporting playback info for this
957ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * user route.
958ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
959ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * <p>If this route manages remote playback, the data exposed by this
960ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * RemoteControlClient will be used to reflect and update information
961ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * such as route volume info in related UIs.</p>
962ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
9631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * <p>The RemoteControlClient must have been previously registered with
9641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.</p>
9651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *
966ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param rcc RemoteControlClient associated with this route
967ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
968ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setRemoteControlClient(RemoteControlClient rcc) {
969ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mRcc = rcc;
9701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            updatePlaybackInfoOnRcc();
971ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
972ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
973ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
9744599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * Retrieve the RemoteControlClient associated with this route, if one has been set.
9754599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         *
9764599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @return the RemoteControlClient associated with this route
9774599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @see #setRemoteControlClient(RemoteControlClient)
9784599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         */
9794599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        public RemoteControlClient getRemoteControlClient() {
9804599696591f745b3a546197d2ba7e5cfc5562484Adam Powell            return mRcc;
9814599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        }
9824599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
9834599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        /**
984ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
985ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
986ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
987ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this route
988ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
989ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
990ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
991ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
992ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
993ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
994ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
995ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
996ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
99771c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this route
998ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
999ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1000ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1001ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
10021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Set a callback to be notified of volume update requests
10051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param vcb
10061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeCallback(VolumeCallback vcb) {
10081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            mVcb = new VolumeCallbackInfo(vcb, this);
10091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether playback associated with this route is "local"
10131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_LOCAL}) or "remote"
10141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_REMOTE}).
10151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param type
10161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackType(int type) {
10181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType != type) {
10191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackType = type;
10201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, type);
10211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether volume for the playback associated with this route is fixed
10261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_FIXED}) or can modified
10271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_VARIABLE}).
10281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeHandling
10291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeHandling(int volumeHandling) {
10311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeHandling != volumeHandling) {
10321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeHandling = volumeHandling;
10331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(
10341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, volumeHandling);
10351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines at what volume the playback associated with this route is performed (for user
10401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * feedback purposes). This information is only used when the playback is not local.
10411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume
10421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolume(int volume) {
10448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            volume = Math.max(0, Math.min(volume, getVolumeMax()));
10451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolume != volume) {
10461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolume = volume;
10471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
10488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
1049f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (mGroup != null) {
1050f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    mGroup.memberVolumeChanged(this);
1051f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
10528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
10538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
10548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
10558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
10568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
10578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
10588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
10598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
10608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
10618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
10628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeSetRequest(this, volume);
10638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
10648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
10658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
10668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
10678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
10688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
10698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
10708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
10718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
10728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
10738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeUpdateRequest(this, direction);
10741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines the maximum volume at which the playback associated with this route is performed
10791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * (for user feedback purposes). This information is only used when the playback is not
10801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * local.
10811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeMax
10821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeMax(int volumeMax) {
10841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeMax != volumeMax) {
10851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeMax = volumeMax;
10861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, volumeMax);
10871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines over what stream type the media is presented.
10921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param stream
10931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackStream(int stream) {
10951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackStream != stream) {
10961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackStream = stream;
10971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_USES_STREAM, stream);
10981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void updatePlaybackInfoOnRcc() {
11021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if ((mRcc != null) && (mRcc.getRcseId() != RemoteControlClient.RCSE_ID_UNREGISTERED)) {
11031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
11041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, mVolumeMax);
11051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
11061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME, mVolume);
11071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
11081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, mVolumeHandling);
11091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
11101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_USES_STREAM, mPlaybackStream);
11111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
11121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, mPlaybackType);
11131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                // let AudioService know whom to call when remote volume needs to be updated
11141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
11151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    sStatic.mAudioService.registerRemoteVolumeObserverForRcc(
11161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            mRcc.getRcseId() /* rccId */, mRemoteVolObserver /* rvo */);
11171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
11181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error registering remote volume observer", e);
11191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
11201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void setPlaybackInfoOnRcc(int what, int value) {
11241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mRcc != null) {
11251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(what, value);
11261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11289a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11299a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11309a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that consists of multiple other routes in a group.
11329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1133b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteGroup extends RouteInfo {
11349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
11359a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private boolean mUpdateName;
11369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup(RouteCategory category) {
11389a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
11399a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroup = this;
11408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
11419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11429a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11430d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
11449a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (mUpdateName) updateName();
11450d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return super.getName(res);
11469a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11479a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11489a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11499a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group. The route must not currently belong to another group.
11509a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
11519a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add to this group
11529a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
11539a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route) {
11549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
11559a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
11569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
11589a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
11599a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
11609a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
11619a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
11629a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
1163d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            final int at = mRoutes.size();
11649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(route);
1165d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
11669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1167f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
11689a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1169f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, at);
11709a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11719a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11729a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11739a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group before the specified index.
11749a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
11759a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add
11769a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param insertAt insert the new route before this index
11779a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
11789a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route, int insertAt) {
11799a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
11809a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
11819a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11829a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
11839a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
11849a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
11859a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
11869a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
11879a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11889a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(insertAt, route);
1189d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
11909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1191f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
11929a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1193f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, insertAt);
11949a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11959a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11969a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11979a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove a route from this group.
11989a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
11999a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to remove
12009a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
12019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(RouteInfo route) {
12029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != this) {
12039a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException("Route " + route +
12049a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        " is not a member of this group.");
12059a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
12069a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.remove(route);
1207d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
12089a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1209f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1210d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
12119a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
12129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12139a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12149a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
12159a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove the route at the specified index from this group.
12169a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
12179a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param index index of the route to remove
12189a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
12199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(int index) {
1220d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            RouteInfo route = mRoutes.remove(index);
1221d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
12229a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1223f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1224d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
12259a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
12269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12279a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1228d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1229d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The number of routes in this group
1230d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1231d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public int getRouteCount() {
1232d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.size();
1233d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1234d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1235d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1236d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the route in this group at the specified index
1237d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1238d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index Index to fetch
1239d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The route at index
1240d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1241d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public RouteInfo getRouteAt(int index) {
1242d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.get(index);
1243d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1244d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1245ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1246ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1247ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1248ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1249ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this group
1250ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1251ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
1252ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
1253ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1254ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1255ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1256ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1257ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1258ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
125971c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this group
1260ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1261ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1262ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1263ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1264ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
12658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
12668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
12678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
12688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
12698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
12708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final float scaledVolume = (float) volume / maxVol;
12738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
12748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
12758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
12768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeVol = (int) (scaledVolume * route.getVolumeMax());
12778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestSetVolume(routeVol);
12788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
12808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
12818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
12828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
12848e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
12868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
12878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
12888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
12898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
12908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
1293f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
12948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
12958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
12968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestUpdateVolume(direction);
1297f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = route.getVolume();
1298f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1299f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1300f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
13018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
13028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
13038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
13048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
13058e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
13068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
13078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
13089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberNameChanged(RouteInfo info, CharSequence name) {
13099a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
13109a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
13119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13129a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13139a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberStatusChanged(RouteInfo info, CharSequence status) {
13149a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
13159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13169a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1317f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void memberVolumeChanged(RouteInfo info) {
1318f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1319f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1320f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1321f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void updateVolume() {
1322f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            // A group always represents the highest component volume value.
1323f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            final int routeCount = getRouteCount();
1324f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
1325f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            for (int i = 0; i < routeCount; i++) {
1326f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = getRouteAt(i).getVolume();
1327f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1328f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1329f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
1330f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1331f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            if (volume != mVolume) {
1332f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                mVolume = volume;
1333f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                dispatchRouteVolumeChanged(this);
1334f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1335f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1336f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1337d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1338d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        void routeUpdated() {
1339d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            int types = 0;
1340d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1341b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            if (count == 0) {
1342b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // Don't keep empty groups in the router.
1343b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                MediaRouter.removeRoute(this);
1344b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                return;
1345b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            }
1346b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
13478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            int maxVolume = 0;
13488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isLocal = true;
13498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isFixedVolume = true;
1350d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
13518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = mRoutes.get(i);
13528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                types |= route.mSupportedTypes;
13538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeMaxVolume = route.getVolumeMax();
13548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (routeMaxVolume > maxVolume) {
13558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    maxVolume = routeMaxVolume;
13568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
13578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
13588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
1359d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
13608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
13618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
1362d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mSupportedTypes = types;
13638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeMax = maxVolume;
1364d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
1365d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            super.routeUpdated();
1366d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
1367d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
13689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void updateName() {
13699a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final StringBuilder sb = new StringBuilder();
13709a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final int count = mRoutes.size();
13719a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
13729a1de308cea2d160778fd977825f10a07b49d738Adam Powell                final RouteInfo info = mRoutes.get(i);
1373b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // TODO: There's probably a much more correct way to localize this.
13749a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (i > 0) sb.append(", ");
13759a1de308cea2d160778fd977825f10a07b49d738Adam Powell                sb.append(info.mName);
13769a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
13779a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = sb.toString();
13789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = false;
13799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1380d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
1381d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1382d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        public String toString() {
1383d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            StringBuilder sb = new StringBuilder(super.toString());
1384d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append('[');
1385d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1386d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
1387d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                if (i > 0) sb.append(", ");
1388d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                sb.append(mRoutes.get(i));
1389d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
1390d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append(']');
1391d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return sb.toString();
1392d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
13939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
13949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13959a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
13969a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Definition of a category of routes. All routes belong to a category.
13979a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1398b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteCategory {
13999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
14000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
14019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mTypes;
14029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final boolean mGroupable;
14039a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteCategory(CharSequence name, int types, boolean groupable) {
14059a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
14069a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mTypes = types;
14079a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroupable = groupable;
14089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14100d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        RouteCategory(int nameResId, int types, boolean groupable) {
14110d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = nameResId;
14120d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mTypes = types;
14130d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mGroupable = groupable;
14140d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
14150d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
14169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14179a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the name of this route category
14189a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
14200d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
14210d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
14220d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
14230d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
14240d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/configuration dependent name of this RouteCategory.
14250d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
14260d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context to resolve name resources
14270d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return the name of this route category
14280d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
14290d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
14300d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
14310d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
14320d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
14330d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
14340d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
14350d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return res.getText(mNameResId);
14360d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
14379a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
14389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14399a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
1441d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the current list of routes in this category that have been added
1442d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * to the MediaRouter.
14439a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1444d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * <p>This list will not include routes that are nested within RouteGroups.
1445d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * A RouteGroup is treated as a single route within its category.</p>
1446d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1447d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param out a List to fill with the routes in this category. If this parameter is
1448d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            non-null, it will be cleared, filled with the current routes with this
1449d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            category, and returned. If this parameter is null, a new List will be
1450d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            allocated to report the category's current routes.
1451d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return A list with the routes in this category that have been added to the MediaRouter.
14529a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
1453d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public List<RouteInfo> getRoutes(List<RouteInfo> out) {
1454d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if (out == null) {
1455d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out = new ArrayList<RouteInfo>();
1456d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            } else {
1457d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out.clear();
1458d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1459d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1460b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = getRouteCountStatic();
1461d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            for (int i = 0; i < count; i++) {
1462b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteInfo route = getRouteAtStatic(i);
1463d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                if (route.mCategory == this) {
1464d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                    out.add(route);
1465d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                }
1466d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1467d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return out;
14689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14699a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14709a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14719a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return Flag set describing the route types supported by this category
14729a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14739a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
14749a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mTypes;
14759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14769a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14789a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Return whether or not this category supports grouping.
14799a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14809a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>If this method returns true, all routes obtained from this category
1481d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
14829a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14839a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return true if this category supports
14849a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14859a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public boolean isGroupable() {
14869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroupable;
14879a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
14909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
1491d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " groupable=" + mGroupable + " }";
14929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
14949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14959a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static class CallbackInfo {
14969a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int type;
1497b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final Callback cb;
1498b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final MediaRouter router;
14999a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1500b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public CallbackInfo(Callback cb, int type, MediaRouter router) {
15019a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.cb = cb;
15029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.type = type;
1503b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            this.router = router;
15049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15059a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
15069a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15079a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
15089a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Interface for receiving events about media routing changes.
15099a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * All methods of this interface will be called from the application's main thread.
15109a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
15119a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A Callback will only receive events relevant to routes that the callback
15129a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * was registered for.</p>
15139a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
15149a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#addCallback(int, Callback)
15159a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#removeCallback(Callback)
15169a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
15170d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static abstract class Callback {
15189a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15199a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes selected as the active route
15209a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
15219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1522d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15239a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been selected
15249a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been selected for the given route types
15259a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15260d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
15279a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15289a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15299a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes unselected as the active route
15309a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
15319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1532d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15339a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been unselected
15349a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been unselected for the given route types
15359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15360d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
15379a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15399a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was added.
15409a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1541d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15429a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has become available for use
15439a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15440d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
15459a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15469a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15479a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was removed.
15489a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1549d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15509a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been removed from availability
15519a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15520d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
15539a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15559a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when an aspect of the indicated route has changed.
15569a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
15579a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>This will not indicate that the types supported by this route have
15589a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * changed, only that cosmetic info such as name or status have been updated.</p>
15599a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1560d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15619a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info The route that was changed
15629a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15630d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
1564d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1565d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1566d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is added to a group.
1567d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1568d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1569d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was added
1570d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was added to
1571d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index The route index within group that info was added at
1572d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
15730d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
15740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                int index);
1575d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1576d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1577d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is removed from a group.
1578d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1579d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1580d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was removed
1581d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was removed from
1582d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
15830d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
15848e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
15868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Called when a route's volume changes.
15878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
15888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param router the MediaRouter reporting the event
15898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param info The route with altered volume
15908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
15918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
15929a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
15939a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15949a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
15950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Stub implementation of {@link MediaRouter.Callback}.
15960d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Each abstract method is defined as a no-op. Override just the ones
15979a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * you need.
15989a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
15990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static class SimpleCallback extends Callback {
16009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1602d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
16039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1606d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
16079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16089a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1610d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteAdded(MediaRouter router, RouteInfo info) {
16119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16129a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16139a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1614d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
16159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16169a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16179a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1618d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteChanged(MediaRouter router, RouteInfo info) {
16199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16209a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16219a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1622d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
1623d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                int index) {
16249a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16259a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1627d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
16289a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1629d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
16308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
16318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
16328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
16339a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
16341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    static class VolumeCallbackInfo {
16361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final VolumeCallback vcb;
16371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final RouteInfo route;
16381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public VolumeCallbackInfo(VolumeCallback vcb, RouteInfo route) {
16401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.vcb = vcb;
16411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.route = route;
16421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
16431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
16441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    /**
16461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * Interface for receiving events about volume changes.
16471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * All methods of this interface will be called from the application's main thread.
16481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
16491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * <p>A VolumeCallback will only receive events relevant to routes that the callback
16501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * was registered for.</p>
16511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
16521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * @see UserRouteInfo#setVolumeCallback(VolumeCallback)
16531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     */
16541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    public static abstract class VolumeCallback {
16551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be increased or decreased.
16571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
16581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param direction an integer indicating whether the volume is to be increased
16591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     (positive value) or decreased (negative value).
16601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     For bundled changes, the absolute value indicates the number of changes
16611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     in the same direction, e.g. +3 corresponds to three "volume up" changes.
16621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeUpdateRequest(RouteInfo info, int direction);
16641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be set to the given value
16661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
16671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume an integer indicating the new volume value that should be used, always
16681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     between 0 and the value set by {@link UserRouteInfo#setVolumeMax(int)}.
16691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeSetRequest(RouteInfo info, int volume);
16711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
16721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static class VolumeChangeReceiver extends BroadcastReceiver {
16748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
16768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onReceive(Context context, Intent intent) {
16778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
16788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
16798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        -1);
16808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (streamType != AudioManager.STREAM_MUSIC) {
16818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
16828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
16838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16848e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
16858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int oldVolume = intent.getIntExtra(
16868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
16878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (newVolume != oldVolume) {
16888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    systemVolumeChanged(newVolume);
16898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
16908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
16928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
16949a1de308cea2d160778fd977825f10a07b49d738Adam Powell}
1695