MediaRouter.java revision 2444ae7e2b8658a4a90f996e678423558744b4a2
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;
25705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.hardware.display.DisplayManager;
26705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.hardware.display.WifiDisplay;
27705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.hardware.display.WifiDisplayStatus;
289a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.os.Handler;
29632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.IBinder;
30632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.RemoteException;
31632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.ServiceManager;
32632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.text.TextUtils;
339a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.util.Log;
34705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.view.Display;
35705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.view.DisplayInfo;
369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
379a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.ArrayList;
389a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.HashMap;
39d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powellimport java.util.List;
4039d5c6172503620ac3761148adac5fd7fa20d02dAdam Powellimport java.util.concurrent.CopyOnWriteArrayList;
419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
429a1de308cea2d160778fd977825f10a07b49d738Adam Powell/**
439a1de308cea2d160778fd977825f10a07b49d738Adam Powell * MediaRouter allows applications to control the routing of media channels
449a1de308cea2d160778fd977825f10a07b49d738Adam Powell * and streams from the current device to external speakers and destination devices.
459a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
46b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
47b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
48b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.MEDIA_ROUTER_SERVICE}.
49b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn *
50b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>The media router API is not thread-safe; all interactions with it must be
51b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * done from the main thread of the process.</p>
529a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
539a1de308cea2d160778fd977825f10a07b49d738Adam Powellpublic class MediaRouter {
549a1de308cea2d160778fd977825f10a07b49d738Adam Powell    private static final String TAG = "MediaRouter";
559a1de308cea2d160778fd977825f10a07b49d738Adam Powell
56b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static class Static {
57b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Resources mResources;
58632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioService mAudioService;
59705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final DisplayManager mDisplayService;
60b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Handler mHandler;
6139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
6239d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell                new CopyOnWriteArrayList<CallbackInfo>();
63b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
64b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
65b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
66b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
67b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final RouteCategory mSystemCategory;
68632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
69705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
70b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
71705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        RouteInfo mDefaultAudioVideo;
72b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mBluetoothA2dpRoute;
73b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
74b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mSelectedRoute;
759a1de308cea2d160778fd977825f10a07b49d738Adam Powell
76705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplayStatus mLastKnownWifiDisplayStatus;
77705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
78705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
79632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
80632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mHandler.post(new Runnable() {
81632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    @Override public void run() {
82705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        updateAudioRoutes(newRoutes);
83632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
84632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                });
85632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
86632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        };
87632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
88b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        Static(Context appContext) {
89b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mResources = Resources.getSystem();
90b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mHandler = new Handler(appContext.getMainLooper());
919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
92632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
93632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            mAudioService = IAudioService.Stub.asInterface(b);
949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
95705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
96705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
97dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            mSystemCategory = new RouteCategory(
98dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                    com.android.internal.R.string.default_audio_route_category_name,
99705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
100705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mSystemCategory.mIsSystem = true;
101b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        }
102b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
103b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        // Called after sStatic is initialized
1048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        void startMonitoringRoutes(Context appContext) {
105705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo = new RouteInfo(mSystemCategory);
106705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
107705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
1082ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            addRouteStatic(mDefaultAudioVideo);
109632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
1102ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // This will select the active wifi display route if there is one.
1112ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
1122ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell
1132ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
1142ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                    new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
1158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            appContext.registerReceiver(new VolumeChangeReceiver(),
1168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
1178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
118705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            AudioRoutesInfo newAudioRoutes = null;
119632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            try {
120705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
121632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            } catch (RemoteException e) {
122632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
123705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newAudioRoutes != null) {
1242ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // This will select the active BT route if there is one and the current
1252ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // selected route is the default system route, or if there is no selected
1262ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // route yet.
127705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateAudioRoutes(newAudioRoutes);
128632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
129705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1302ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // Select the default route if the above didn't sync us up
1312ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // appropriately with relevant system state.
1322ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            if (mSelectedRoute == null) {
1332ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo);
1342ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            }
135632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        }
136632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
137705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        void updateAudioRoutes(AudioRoutesInfo newRoutes) {
138705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
139705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
140632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                int name;
141632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
142632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
143632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_headphones;
144632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
145632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
146632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
1474131a37366d59b5e61f55c4e48d2b22ee0c4cad4Adam Powell                    name = com.android.internal.R.string.default_media_route_name_hdmi;
148632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else {
149632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name;
150632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
151705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDefaultAudioVideo.mNameResId = name;
152705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                dispatchRouteChanged(sStatic.mDefaultAudioVideo);
153632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
154bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
1553f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell            final int mainType = mCurAudioRoutesInfo.mMainType;
1563f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell
157bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            boolean a2dpEnabled;
158bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            try {
159bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                a2dpEnabled = mAudioService.isBluetoothA2dpOn();
160bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            } catch (RemoteException e) {
161bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                Log.e(TAG, "Error querying Bluetooth A2DP state", e);
162bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                a2dpEnabled = false;
163bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
164bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
165705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
166705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
167705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                if (mCurAudioRoutesInfo.mBluetoothName != null) {
168632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    if (sStatic.mBluetoothA2dpRoute == null) {
169632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
170705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        info.mName = mCurAudioRoutesInfo.mBluetoothName;
171632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
172632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        sStatic.mBluetoothA2dpRoute = info;
1732ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                        addRouteStatic(sStatic.mBluetoothA2dpRoute);
174632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    } else {
175705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
176632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
177632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
178632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if (sStatic.mBluetoothA2dpRoute != null) {
179632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    removeRoute(sStatic.mBluetoothA2dpRoute);
180632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    sStatic.mBluetoothA2dpRoute = null;
181632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
182632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
183bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
184bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            if (mBluetoothA2dpRoute != null) {
1853f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell                if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
1863f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell                        mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
187705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
1883f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell                } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
1892ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                        a2dpEnabled) {
190bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
191bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                }
192bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
193b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        }
194b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
1959a1de308cea2d160778fd977825f10a07b49d738Adam Powell
196b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static Static sStatic;
1979a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1989a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
1999a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for live audio.
2009a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2019a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A device that supports live audio routing will allow the media audio stream
2029a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to be routed to supported destinations. This can include internal speakers or
2039a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * audio jacks on the device itself, A2DP devices, and more.</p>
2049a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2059a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Once initiated this routing is transparent to the application. All audio
2069a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * played on the media stream will be routed to the selected destination.</p>
2079a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
2099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2109a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
211705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Route type flag for live video.
212705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     *
213705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * <p>A device that supports live video routing will allow a mirrored version
214705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * of the device's primary display or a customized
215705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
216705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     *
217705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * <p>Once initiated, display mirroring is transparent to the application.
218705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * While remote routing is active the application may use a
219705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * {@link android.app.Presentation Presentation} to replace the mirrored view
220705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * on the external display with different content.</p>
221705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     */
222705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
223705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
224705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    /**
2259a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for application-specific usage.
2269a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2279a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Unlike other media route types, user routes are managed by the application.
2289a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * The MediaRouter will manage and dispatch events for user routes, but the application
2299a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * is expected to interpret the meaning of these events and perform the requested
2309a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * routing tasks.</p>
2319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2329a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_USER = 0x00800000;
2339a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2349a1de308cea2d160778fd977825f10a07b49d738Adam Powell    // Maps application contexts
2359a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
2369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2379a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static String typesToString(int types) {
2389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final StringBuilder result = new StringBuilder();
2399a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
2409a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_LIVE_AUDIO ");
2419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_USER) != 0) {
2439a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_USER ");
2449a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2459a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return result.toString();
2469a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2479a1de308cea2d160778fd977825f10a07b49d738Adam Powell
248b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    /** @hide */
249b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public MediaRouter(Context context) {
250b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        synchronized (Static.class) {
251b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic == null) {
2528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final Context appContext = context.getApplicationContext();
2538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic = new Static(appContext);
2548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic.startMonitoringRoutes(appContext);
255b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            }
2569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2579a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2589a1de308cea2d160778fd977825f10a07b49d738Adam Powell
259690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
260690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @hide for use by framework routing UI
261690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
262690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSystemAudioRoute() {
263705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return sStatic.mDefaultAudioVideo;
264690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
265690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
266690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
2674599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     * @hide for use by framework routing UI
2684599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     */
2694599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    public RouteCategory getSystemAudioCategory() {
2704599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        return sStatic.mSystemCategory;
2714599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    }
2724599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
2734599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    /**
274690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Return the currently selected route for the given types
275690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
276690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @param type route types
277690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @return the selected route
278690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
279690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSelectedRoute(int type) {
280b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mSelectedRoute;
281690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
282690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
2839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
2849a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add a callback to listen to events about specific kinds of media routes.
2859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * If the specified callback is already registered, its registration will be updated for any
2869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * additional route types specified.
2879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param types Types of routes this callback is interested in
2899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to add
2909a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2919a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addCallback(int types, Callback cb) {
292b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
2939a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
294b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final CallbackInfo info = sStatic.mCallbacks.get(i);
2959a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (info.cb == cb) {
296dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell                info.type |= types;
2979a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
2989a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
2999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
300b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mCallbacks.add(new CallbackInfo(cb, types, this));
3019a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3029a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3039a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3049a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove the specified callback. It will no longer receive events about media routing.
3059a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3069a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to remove
3079a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeCallback(Callback cb) {
309b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
3109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
311b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic.mCallbacks.get(i).cb == cb) {
312b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCallbacks.remove(i);
3139a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
3149a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
3159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
3179a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
319d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    /**
320d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * Select the specified route to use for output of the given media types.
321d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *
322d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param types type flags indicating which types this route should be used for.
323d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *              The route must support at least a subset.
324d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param route Route to select
325d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     */
3269a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void selectRoute(int types, RouteInfo route) {
3270d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        // Applications shouldn't programmatically change anything but user routes.
3280d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        types &= ROUTE_TYPE_USER;
3290d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        selectRouteStatic(types, route);
3300d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
3310d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
3320d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
3330d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @hide internal use
3340d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
3350d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public void selectRouteInt(int types, RouteInfo route) {
336b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        selectRouteStatic(types, route);
337b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
338b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
339b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void selectRouteStatic(int types, RouteInfo route) {
340705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final RouteInfo oldRoute = sStatic.mSelectedRoute;
341705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (oldRoute == route) return;
3420d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        if ((route.getSupportedTypes() & types) == 0) {
3430d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
3440d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(route.getSupportedTypes()) + " into route types " +
3450d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(types));
3464ee1f55ce0f4909a7430ab44563a81852f335071Adam Powell            return;
3470d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
3489a1de308cea2d160778fd977825f10a07b49d738Adam Powell
349dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
350dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
351705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
352dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            try {
353dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
354dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            } catch (RemoteException e) {
355dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                Log.e(TAG, "Error changing Bluetooth A2DP state", e);
356dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            }
357dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        }
358dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell
359705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final WifiDisplay activeDisplay =
360705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
361705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
362705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
363705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
364705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
365705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
366705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            } else if (activeDisplay != null && !newRouteHasAddress) {
367705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.disconnectWifiDisplay();
368705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
369705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
370705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
371705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (oldRoute != null) {
3729a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
373705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
3749a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
375b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mSelectedRoute = route;
3769a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (route != null) {
3779a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
3789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteSelected(types & route.getSupportedTypes(), route);
3799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3809a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3819a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
383705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Compare the device address of a display and a route.
384705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Nulls/no device address will match another null/no address.
385705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     */
386705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
387705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
388705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display == null && !routeHasAddress) {
389705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return true;
390705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
391705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
392705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display != null && routeHasAddress) {
393705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return display.getDeviceAddress().equals(info.mDeviceAddress);
394705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
395705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return false;
396705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
397705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
398705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    /**
3999a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add an app-specified route for media to the MediaRouter.
4009a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
4019a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4029a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to add
4039a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createUserRoute()
4049a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
4059a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4069a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addUserRoute(UserRouteInfo info) {
4072ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        addRouteStatic(info);
4089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
410d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
411d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide Framework use only
412d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
413d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void addRouteInt(RouteInfo info) {
4142ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        addRouteStatic(info);
415d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
416d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
4172ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell    static void addRouteStatic(RouteInfo info) {
4189a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory cat = info.getCategory();
419b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (!sStatic.mCategories.contains(cat)) {
420b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mCategories.add(cat);
4219a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
422d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        if (cat.isGroupable() && !(info instanceof RouteGroup)) {
4239a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // Enforce that any added route in a groupable category must be in a group.
4249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteGroup group = new RouteGroup(info.getCategory());
425dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell            group.mSupportedTypes = info.mSupportedTypes;
426b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(group);
427d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(group);
428b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            group.addRoute(info);
429d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
4309a1de308cea2d160778fd977825f10a07b49d738Adam Powell            info = group;
431d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        } else {
432b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(info);
433d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(info);
4349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
4359a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4379a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4389a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove an app-specified route for media from the MediaRouter.
4399a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4409a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to remove
4419a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
4429a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4439a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeUserRoute(UserRouteInfo info) {
4449a1de308cea2d160778fd977825f10a07b49d738Adam Powell        removeRoute(info);
4459a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4469a1de308cea2d160778fd977825f10a07b49d738Adam Powell
447690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
448690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Remove all app-specified routes from the MediaRouter.
449690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
450690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @see #removeUserRoute(UserRouteInfo)
451690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
452690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public void clearUserRoutes() {
453b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        for (int i = 0; i < sStatic.mRoutes.size(); i++) {
454b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.get(i);
455d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // TODO Right now, RouteGroups only ever contain user routes.
456d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // The code below will need to change if this assumption does.
457d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
458690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                removeRouteAt(i);
459690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                i--;
460690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
461690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
462690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
463690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
464d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
465d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide internal use only
466d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
467d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void removeRouteInt(RouteInfo info) {
468d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        removeRoute(info);
469d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
470d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
471b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void removeRoute(RouteInfo info) {
472b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mRoutes.remove(info)) {
4739a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteCategory removingCat = info.getCategory();
474b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
4759a1de308cea2d160778fd977825f10a07b49d738Adam Powell            boolean found = false;
4769a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
477b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
4789a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (removingCat == cat) {
4799a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    found = true;
4809a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    break;
4819a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
4829a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
483d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
484d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
485d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
486705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo);
487d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
4889a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!found) {
489b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
4909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
4919a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteRemoved(info);
4929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
4939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
495690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    void removeRouteAt(int routeIndex) {
496b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
497b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
498690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            final RouteCategory removingCat = info.getCategory();
499b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
500690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            boolean found = false;
501690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            for (int i = 0; i < count; i++) {
502b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
503690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                if (removingCat == cat) {
504690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    found = true;
505690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    break;
506690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                }
507690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
508d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
509d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
510d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
511705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
512705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        sStatic.mDefaultAudioVideo);
513d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
514690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            if (!found) {
515b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
516690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
517690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            dispatchRouteRemoved(info);
518690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
519690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
520690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
5219a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5229a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteCategory categories} currently
5239a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * represented by routes known to this MediaRouter.
5249a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5259a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of unique categories represented by this MediaRouter's known routes
5269a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5279a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getCategoryCount() {
528b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.size();
5299a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5309a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5319a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the {@link MediaRouter.RouteCategory category} at the given index.
5339a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Valid indices are in the range [0-getCategoryCount).
5349a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5359a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index which category to return
5369a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the category at index
5379a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5389a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory getCategoryAt(int index) {
539b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.get(index);
5409a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5429a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteInfo routes} currently known
5449a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to this MediaRouter.
5459a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5469a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of routes tracked by this router
5479a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5489a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getRouteCount() {
549b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
5509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5519a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5529a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5539a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the route at the specified index.
5549a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5559a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index index of the route to return
5569a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the route at index
5579a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5589a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteInfo getRouteAt(int index) {
559b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
560b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
561b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
562b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static int getRouteCountStatic() {
563b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
564b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
565b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
566b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static RouteInfo getRouteAtStatic(int index) {
567b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
5689a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5699a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5709a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5719a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new user route that may be modified and registered for use by the application.
5729a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5739a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param category The category the new route will belong to
5749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return A new UserRouteInfo for use by the application
5759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
5779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
5789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createRouteCategory(CharSequence)
5799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5809a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public UserRouteInfo createUserRoute(RouteCategory category) {
5819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new UserRouteInfo(category);
5829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5849a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new route category. Each route must belong to a category.
5869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param name Name of the new category
5889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
5899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the new RouteCategory
5909a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5919a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
5929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
5939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5940d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
5950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
5960d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Create a new route category. Each route must belong to a category.
5970d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     *
5980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param nameResId Resource ID of the name of the new category
5990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
6000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @return the new RouteCategory
6010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
6020d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
6030d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
6040d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
6059a1de308cea2d160778fd977825f10a07b49d738Adam Powell
606b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void updateRoute(final RouteInfo info) {
6079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        dispatchRouteChanged(info);
6089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
610b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteSelected(int type, RouteInfo info) {
61139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6129a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
613b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteSelected(cbi.router, type, info);
6149a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6169a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6179a1de308cea2d160778fd977825f10a07b49d738Adam Powell
618b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUnselected(int type, RouteInfo info) {
61939d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6209a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
621b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUnselected(cbi.router, type, info);
6229a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6249a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6259a1de308cea2d160778fd977825f10a07b49d738Adam Powell
626b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteChanged(RouteInfo info) {
62739d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6289a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
629b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteChanged(cbi.router, info);
6309a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6319a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6329a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6339a1de308cea2d160778fd977825f10a07b49d738Adam Powell
634b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteAdded(RouteInfo info) {
63539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6369a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
637b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteAdded(cbi.router, info);
6389a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6399a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6409a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
642b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteRemoved(RouteInfo info) {
64339d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6449a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
645b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteRemoved(cbi.router, info);
6469a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6479a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6489a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6499a1de308cea2d160778fd977825f10a07b49d738Adam Powell
650b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
65139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
652d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
653b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteGrouped(cbi.router, info, group, index);
654d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
655d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
656d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    }
657d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
658b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
65939d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
660d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
661b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUngrouped(cbi.router, info, group);
6629a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6649a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6659a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void dispatchRouteVolumeChanged(RouteInfo info) {
6678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
6698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                cbi.cb.onRouteVolumeChanged(cbi.router, info);
6708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
6718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
6728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
6738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void systemVolumeChanged(int newValue) {
6758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        final RouteInfo selectedRoute = sStatic.mSelectedRoute;
6768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == null) return;
6778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
679705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectedRoute == sStatic.mDefaultAudioVideo) {
6808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(selectedRoute);
6818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else if (sStatic.mBluetoothA2dpRoute != null) {
6828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            try {
6838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
684705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
6858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } catch (RemoteException e) {
6868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
6878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
6888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else {
689705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
690705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
691705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
692705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
693705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
694705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
695705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
696705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        // TODO Naive implementation. Make this smarter later.
697b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell        boolean wantScan = false;
698b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell        boolean blockScan = false;
699705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplay[] oldDisplays = oldStatus != null ?
700705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                oldStatus.getRememberedDisplays() : new WifiDisplay[0];
701705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplay[] newDisplays = newStatus.getRememberedDisplays();
702705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays();
7032ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        WifiDisplay activeDisplay = newStatus.getActiveDisplay();
704705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
705705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < newDisplays.length; i++) {
706705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay d = newDisplays[i];
707705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays);
708705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (oldRemembered == null) {
70911b999d0aaca72a33526795b6849b473cc3dd569Adam Powell                addRouteStatic(makeWifiDisplayRoute(d,
71011b999d0aaca72a33526795b6849b473cc3dd569Adam Powell                        findMatchingDisplay(d, availableDisplays) != null));
711b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                wantScan = true;
712705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            } else {
713705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                final boolean available = findMatchingDisplay(d, availableDisplays) != null;
714705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                final RouteInfo route = findWifiDisplayRoute(d);
715705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateWifiDisplayRoute(route, d, available, newStatus);
716705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
7172ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            if (d.equals(activeDisplay)) {
7182ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                final RouteInfo activeRoute = findWifiDisplayRoute(d);
7192ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                if (activeRoute != null) {
7202ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                    selectRouteStatic(activeRoute.getSupportedTypes(), activeRoute);
721b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell
722b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                    // Don't scan if we're already connected to a wifi display,
723b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                    // the scanning process can cause a hiccup with some configurations.
724b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                    blockScan = true;
7252ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                }
7262ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            }
727705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
728705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < oldDisplays.length; i++) {
729705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay d = oldDisplays[i];
730705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
731705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newDisplay == null) {
732705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                removeRoute(findWifiDisplayRoute(d));
733705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
734705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
735705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
736b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell        if (wantScan && !blockScan) {
737705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            sStatic.mDisplayService.scanWifiDisplays();
738705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
739705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
740705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        sStatic.mLastKnownWifiDisplayStatus = newStatus;
741705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
742705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
74311b999d0aaca72a33526795b6849b473cc3dd569Adam Powell    static RouteInfo makeWifiDisplayRoute(WifiDisplay display, boolean available) {
744705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
745705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mDeviceAddress = display.getDeviceAddress();
746705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
747705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
748705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
74911b999d0aaca72a33526795b6849b473cc3dd569Adam Powell
75011b999d0aaca72a33526795b6849b473cc3dd569Adam Powell        newRoute.setStatusCode(available ?
75111b999d0aaca72a33526795b6849b473cc3dd569Adam Powell                RouteInfo.STATUS_AVAILABLE : RouteInfo.STATUS_CONNECTING);
75211b999d0aaca72a33526795b6849b473cc3dd569Adam Powell        newRoute.mEnabled = available;
753705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
7542444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        newRoute.mName = display.getFriendlyDisplayName();
755705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return newRoute;
756705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
757705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
758705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display,
759705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            boolean available, WifiDisplayStatus wifiDisplayStatus) {
760705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean isScanning =
761705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING;
762705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
763705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean changed = false;
764705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        int newStatus = RouteInfo.STATUS_NONE;
765705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
766705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (available) {
767705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE;
768705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        } else {
769705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
770705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
771705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
772705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display.equals(wifiDisplayStatus.getActiveDisplay())) {
773705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final int activeState = wifiDisplayStatus.getActiveDisplayState();
774705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            switch (activeState) {
775705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
776705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    newStatus = RouteInfo.STATUS_NONE;
777705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
778705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
779705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    newStatus = RouteInfo.STATUS_CONNECTING;
780705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
781705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
782705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    Log.e(TAG, "Active display is not connected!");
783705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
784705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
785705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
786705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
7872444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        final String newName = display.getFriendlyDisplayName();
7882444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        if (!route.getName().equals(newName)) {
789705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            route.mName = newName;
790705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            changed = true;
791705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
792705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
793705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        changed |= route.mEnabled != available;
794705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        route.mEnabled = available;
795705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
796705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        changed |= route.setStatusCode(newStatus);
797705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
798705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (changed) {
799705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteChanged(route);
800705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
801705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
802705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (!available && route == sStatic.mSelectedRoute) {
803705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            // Oops, no longer available. Reselect the default.
804705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
805705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
806705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
807705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
808705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
8092444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown    private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) {
810705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < displays.length; i++) {
8112444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown            final WifiDisplay other = displays[i];
8122444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown            if (d.getDeviceAddress().equals(other.getDeviceAddress())) {
8132444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown                return other;
814705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
815705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
816705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return null;
817705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
818705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
819705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
820705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final int count = sStatic.mRoutes.size();
821705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < count; i++) {
822705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final RouteInfo info = sStatic.mRoutes.get(i);
823705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
824705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                return info;
825705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
826705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
827705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return null;
8288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
8298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
8309a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
8319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a media route.
8329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
833b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteInfo {
8349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
8350d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
8369a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private CharSequence mStatus;
8379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mSupportedTypes;
8389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup mGroup;
8399a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory mCategory;
840ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        Drawable mIcon;
8411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        // playback information
8421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackType = PLAYBACK_TYPE_LOCAL;
8431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
8441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
8451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
8461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackStream = AudioManager.STREAM_MUSIC;
8471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        VolumeCallbackInfo mVcb;
8489a1de308cea2d160778fd977825f10a07b49d738Adam Powell
849705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        String mDeviceAddress;
850705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean mEnabled = true;
851705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
852705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        // A predetermined connection status that can override mStatus
853705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        private int mStatusCode;
854705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
8552ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_NONE = 0;
8562ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_SCANNING = 1;
8572ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_CONNECTING = 2;
8582ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_AVAILABLE = 3;
8592ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
860705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
861b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        private Object mTag;
862b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
8631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * The default playback type, "local", indicating the presentation of the media is happening
8651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * on the same device (e.g. a phone, a tablet) as where it is controlled from.
8661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
8671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_LOCAL = 0;
8691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * A playback type indicating the presentation of the media is happening on
8711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * a different device (i.e. the remote device) than where it is controlled from.
8721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
8731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_REMOTE = 1;
8751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is fixed, i.e. it cannot be
8771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * controlled from this object. An example of fixed playback volume is a remote player,
8781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
8791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * than attenuate at the source.
8801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setVolumeHandling(int)
8811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_FIXED = 0;
8831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is variable and can be controlled
8851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * from this object.
8861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_VARIABLE = 1;
8881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteInfo(RouteCategory category) {
8909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mCategory = category;
8919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
8929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
8939a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
8949a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly name of a media route. This is the string presented
8959a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * to users who may select this as the active route.
8969a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
8979a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
8980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
8990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
9010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
9020d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/resource selected name of this route.
9030d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
9040d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context used to resolve the correct configuration to load
9050d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return The user-friendly name of the media route. This is the string presented
9060d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * to users who may select this as the active route.
9070d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
9080d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
9090d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
9100d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9110d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
9120d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
9130d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
9140d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return mName = res.getText(mNameResId);
9150d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
9169a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
9179a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9209a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly status for a media route. This may include a description
9219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of the currently playing media, if available.
9229a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getStatus() {
9249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mStatus;
9259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9269a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
928705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * Set this route's status by predetermined status code. If the caller
929705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * should dispatch a route changed event this call will return true;
930705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
931705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean setStatusCode(int statusCode) {
932705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (statusCode != mStatusCode) {
933705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mStatusCode = statusCode;
934705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                int resId = 0;
935705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                switch (statusCode) {
936705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_SCANNING:
937705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_scanning;
938705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
939705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_CONNECTING:
940705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_connecting;
941705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
942705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_AVAILABLE:
943705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_available;
944705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
945705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_NOT_AVAILABLE:
946705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_not_available;
947705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
948705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                }
949705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
950705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                return true;
951705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
952705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return false;
953705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
954705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
955705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
9562ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell         * @hide
9572ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell         */
9582ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        public int getStatusCode() {
9592ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            return mStatusCode;
9602ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        }
9612ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell
9622ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /**
9639a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return A media type flag set describing which types this route supports.
9649a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
9669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mSupportedTypes;
9679a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9689a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9709a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The group that this route belongs to.
9719a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9729a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteGroup getGroup() {
9739a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroup;
9749a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9759a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9769a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9779a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the category this route belongs to.
9789a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteCategory getCategory() {
9809a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mCategory;
9819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
983ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
984ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Get the icon representing this route.
985ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * This icon will be used in picker UIs if available.
986ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
987ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @return the icon representing this route or null if no icon is available
988ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
989ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public Drawable getIconDrawable() {
990ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            return mIcon;
991ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
992ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
993b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
994b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * Set an application-specific tag object for this route.
995b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * The application may use this to store arbitrary data associated with the
996b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * route for internal tracking.
997b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
998b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * <p>Note that the lifespan of a route may be well past the lifespan of
999b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * an Activity or other Context; take care that objects you store here
1000b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * will not keep more data in memory alive than you intend.</p>
1001b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
1002b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @param tag Arbitrary, app-specific data for this route to hold for later use
1003b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
1004b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public void setTag(Object tag) {
1005b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            mTag = tag;
1006130b4572d1f3df702e5b296a655d15a41f6d4c66Adam Powell            routeUpdated();
1007b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
1008b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
1009b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
1010b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @return The tag object previously set by the application
1011b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @see #setTag(Object)
1012b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
1013b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public Object getTag() {
1014b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            return mTag;
1015b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
1016b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
10171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the type of playback associated with this route
10191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackType(int)
10201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackType() {
10221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackType;
10231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the stream over which the playback associated with this route is performed
10271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackStream(int)
10281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackStream() {
10301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackStream;
10311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Return the current volume for this route. Depending on the route, this may only
10358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * be valid if the route is currently selected.
10368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
10371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the volume at which the playback associated with this route is performed
10381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolume(int)
10391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolume() {
10411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
10421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int vol = 0;
10431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
10441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream);
10451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
10461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
10471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
10481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return vol;
10491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
10501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolume;
10511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request a volume change for this route.
10568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param volume value between 0 and getVolumeMax
10578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
10588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
10598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
10608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
10618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
10628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
10638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
10648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
10658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
10668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
10678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
10688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
10698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
10708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
10718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
10728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
10738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request an incremental volume update for this route.
10748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param direction Delta to apply to the current volume
10758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
10768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
10778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
10788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
10798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    final int volume =
10808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                            Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
10818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
10828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
10838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
10848e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
10858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
10868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
10878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
10888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
10898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
10908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
10918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
10928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
10931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the maximum volume at which the playback associated with this route is performed
10941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeMax(int)
10951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeMax() {
10971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
10981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int volMax = 0;
10991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
11001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    volMax = sStatic.mAudioService.getStreamMaxVolume(mPlaybackStream);
11011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
11021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
11031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
11041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return volMax;
11051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
11061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolumeMax;
11071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
11111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return how volume is handling on the route
11121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeHandling(int)
11131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
11141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeHandling() {
11151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mVolumeHandling;
11161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
1118705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
1119705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @return true if this route is enabled and may be selected
1120705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
1121705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public boolean isEnabled() {
1122705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return mEnabled;
1123705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1124705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
11259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void setStatusInt(CharSequence status) {
11269a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!status.equals(mStatus)) {
11279a1de308cea2d160778fd977825f10a07b49d738Adam Powell                mStatus = status;
11289a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (mGroup != null) {
11299a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    mGroup.memberStatusChanged(this, status);
11309a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
11319a1de308cea2d160778fd977825f10a07b49d738Adam Powell                routeUpdated();
11329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11349a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
11361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
11371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                sStatic.mHandler.post(new Runnable() {
11381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    @Override
11391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    public void run() {
11401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        if (mVcb != null) {
11411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            if (direction != 0) {
11421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
11431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            } else {
11441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeSetRequest(mVcb.route, value);
11451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            }
11461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        }
11471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    }
11481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                });
11491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        };
11511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11529a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void routeUpdated() {
11539a1de308cea2d160778fd977825f10a07b49d738Adam Powell            updateRoute(this);
11549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11559a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
11579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
1158d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            String supportedTypes = typesToString(getSupportedTypes());
1159d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() +
1160d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " category=" + getCategory() +
11619a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    " supportedTypes=" + supportedTypes + "}";
11629a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11639a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11649a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11659a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that the application may define and modify.
11678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
11688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
11699a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11709a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter.RouteInfo
11719a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1172b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class UserRouteInfo extends RouteInfo {
1173ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        RemoteControlClient mRcc;
11749a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        UserRouteInfo(RouteCategory category) {
11769a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
11779a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mSupportedTypes = ROUTE_TYPE_USER;
11788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = PLAYBACK_TYPE_REMOTE;
11798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
11809a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11819a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11829a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11839a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the user-visible name of this route.
11849a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param name Name to display to the user to describe this route
11859a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
11869a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setName(CharSequence name) {
11879a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
11889a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
11899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
11910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
11920d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Set the user-visible name of this route.
11930d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param resId Resource ID of the name to display to the user to describe this route
11940d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
11950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public void setName(int resId) {
11960d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = resId;
11970d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mName = null;
11980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            routeUpdated();
11990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
12009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
12029a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the current user-visible status for this route.
12039a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param status Status to display to the user to describe what the endpoint
12049a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of this route is currently doing
12059a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
12069a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setStatus(CharSequence status) {
12079a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
12089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1209ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1210ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1211ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set the RemoteControlClient responsible for reporting playback info for this
1212ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * user route.
1213ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1214ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * <p>If this route manages remote playback, the data exposed by this
1215ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * RemoteControlClient will be used to reflect and update information
1216ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * such as route volume info in related UIs.</p>
1217ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
12181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * <p>The RemoteControlClient must have been previously registered with
12191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.</p>
12201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *
1221ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param rcc RemoteControlClient associated with this route
1222ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1223ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setRemoteControlClient(RemoteControlClient rcc) {
1224ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mRcc = rcc;
12251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            updatePlaybackInfoOnRcc();
1226ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1227ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1228ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
12294599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * Retrieve the RemoteControlClient associated with this route, if one has been set.
12304599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         *
12314599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @return the RemoteControlClient associated with this route
12324599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @see #setRemoteControlClient(RemoteControlClient)
12334599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         */
12344599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        public RemoteControlClient getRemoteControlClient() {
12354599696591f745b3a546197d2ba7e5cfc5562484Adam Powell            return mRcc;
12364599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        }
12374599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
12384599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        /**
1239ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
1240ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1241ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1242ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this route
1243ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1244ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
1245ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
1246ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1247ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1248ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1249ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
1250ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1251ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
125271c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this route
1253ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1254ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1255ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1256ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
12571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
12581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
12591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Set a callback to be notified of volume update requests
12601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param vcb
12611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
12621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeCallback(VolumeCallback vcb) {
12631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            mVcb = new VolumeCallbackInfo(vcb, this);
12641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
12651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
12661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
12671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether playback associated with this route is "local"
12681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_LOCAL}) or "remote"
12691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_REMOTE}).
12701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param type
12711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
12721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackType(int type) {
12731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType != type) {
12741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackType = type;
12751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, type);
12761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
12771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
12781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
12791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
12801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether volume for the playback associated with this route is fixed
12811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_FIXED}) or can modified
12821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_VARIABLE}).
12831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeHandling
12841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
12851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeHandling(int volumeHandling) {
12861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeHandling != volumeHandling) {
12871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeHandling = volumeHandling;
12881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(
12891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, volumeHandling);
12901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
12911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
12921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
12931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
12941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines at what volume the playback associated with this route is performed (for user
12951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * feedback purposes). This information is only used when the playback is not local.
12961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume
12971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
12981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolume(int volume) {
12998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            volume = Math.max(0, Math.min(volume, getVolumeMax()));
13001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolume != volume) {
13011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolume = volume;
13021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
13038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
1304f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (mGroup != null) {
1305f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    mGroup.memberVolumeChanged(this);
1306f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
13078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
13088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
13098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
13108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
13118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
13128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
13138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
13148e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
13158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
13168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
13178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeSetRequest(this, volume);
13188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
13198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
13208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
13218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
13228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
13238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
13248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
13258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
13268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
13278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
13288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeUpdateRequest(this, direction);
13291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
13301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
13331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines the maximum volume at which the playback associated with this route is performed
13341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * (for user feedback purposes). This information is only used when the playback is not
13351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * local.
13361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeMax
13371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
13381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeMax(int volumeMax) {
13391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeMax != volumeMax) {
13401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeMax = volumeMax;
13411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, volumeMax);
13421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
13431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
13461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines over what stream type the media is presented.
13471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param stream
13481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
13491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackStream(int stream) {
13501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackStream != stream) {
13511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackStream = stream;
13521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_USES_STREAM, stream);
13531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
13541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void updatePlaybackInfoOnRcc() {
13571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if ((mRcc != null) && (mRcc.getRcseId() != RemoteControlClient.RCSE_ID_UNREGISTERED)) {
13581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
13591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, mVolumeMax);
13601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
13611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME, mVolume);
13621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
13631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, mVolumeHandling);
13641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
13651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_USES_STREAM, mPlaybackStream);
13661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
13671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, mPlaybackType);
13681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                // let AudioService know whom to call when remote volume needs to be updated
13691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
13701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    sStatic.mAudioService.registerRemoteVolumeObserverForRcc(
13711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            mRcc.getRcseId() /* rccId */, mRemoteVolObserver /* rvo */);
13721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
13731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error registering remote volume observer", e);
13741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
13751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
13761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void setPlaybackInfoOnRcc(int what, int value) {
13791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mRcc != null) {
13801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(what, value);
13811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
13821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
13849a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13859a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
13869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that consists of multiple other routes in a group.
13879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1388b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteGroup extends RouteInfo {
13899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
13909a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private boolean mUpdateName;
13919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup(RouteCategory category) {
13939a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
13949a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroup = this;
13958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
13969a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13979a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
13999a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (mUpdateName) updateName();
14000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return super.getName(res);
14019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14029a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14049a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group. The route must not currently belong to another group.
14059a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14069a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add to this group
14079a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route) {
14099a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
14109a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
14119a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
14129a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
14139a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
14149a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
14159a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
14169a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
14179a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
1418d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            final int at = mRoutes.size();
14199a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(route);
1420d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
14219a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1422f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
14239a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1424f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, at);
14259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14269a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group before the specified index.
14299a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14309a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add
14319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param insertAt insert the new route before this index
14329a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route, int insertAt) {
14349a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
14359a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
14369a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
14379a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
14389a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
14399a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
14409a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
14419a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
14429a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
14439a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(insertAt, route);
1444d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
14459a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1446f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
14479a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1448f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, insertAt);
14499a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14509a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14519a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14529a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove a route from this group.
14539a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14549a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to remove
14559a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(RouteInfo route) {
14579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != this) {
14589a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException("Route " + route +
14599a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        " is not a member of this group.");
14609a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
14619a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.remove(route);
1462d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
14639a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1464f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1465d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
14669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
14679a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14689a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14709a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove the route at the specified index from this group.
14719a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14729a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param index index of the route to remove
14739a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14749a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(int index) {
1475d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            RouteInfo route = mRoutes.remove(index);
1476d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
14779a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1478f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1479d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
14809a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
14819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1483d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1484d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The number of routes in this group
1485d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1486d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public int getRouteCount() {
1487d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.size();
1488d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1489d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1490d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1491d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the route in this group at the specified index
1492d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1493d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index Index to fetch
1494d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The route at index
1495d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1496d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public RouteInfo getRouteAt(int index) {
1497d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.get(index);
1498d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1499d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1500ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1501ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1502ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1503ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1504ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this group
1505ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1506ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
1507ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
1508ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1509ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1510ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1511ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1512ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1513ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
151471c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this group
1515ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1516ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1517ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1518ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1519ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
15208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
15218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
15228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
15238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
15248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
15258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
15268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final float scaledVolume = (float) volume / maxVol;
15288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
15298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
15308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
15318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeVol = (int) (scaledVolume * route.getVolumeMax());
15328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestSetVolume(routeVol);
15338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
15348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
15358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
15368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
15378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
15388e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
15398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
15418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
15428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
15438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
15448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
15458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
15468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
1548f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
15498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
15508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
15518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestUpdateVolume(direction);
1552f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = route.getVolume();
1553f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1554f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1555f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
15568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
15578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
15588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
15598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
15608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
15618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
15628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberNameChanged(RouteInfo info, CharSequence name) {
15649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
15659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
15669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15679a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberStatusChanged(RouteInfo info, CharSequence status) {
15699a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
15709a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15719a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1572f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void memberVolumeChanged(RouteInfo info) {
1573f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1574f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1575f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1576f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void updateVolume() {
1577f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            // A group always represents the highest component volume value.
1578f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            final int routeCount = getRouteCount();
1579f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
1580f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            for (int i = 0; i < routeCount; i++) {
1581f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = getRouteAt(i).getVolume();
1582f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1583f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1584f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
1585f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1586f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            if (volume != mVolume) {
1587f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                mVolume = volume;
1588f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                dispatchRouteVolumeChanged(this);
1589f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1590f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1591f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1592d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1593d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        void routeUpdated() {
1594d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            int types = 0;
1595d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1596b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            if (count == 0) {
1597b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // Don't keep empty groups in the router.
1598b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                MediaRouter.removeRoute(this);
1599b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                return;
1600b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            }
1601b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
16028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            int maxVolume = 0;
16038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isLocal = true;
16048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isFixedVolume = true;
1605d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
16068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = mRoutes.get(i);
16078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                types |= route.mSupportedTypes;
16088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeMaxVolume = route.getVolumeMax();
16098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (routeMaxVolume > maxVolume) {
16108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    maxVolume = routeMaxVolume;
16118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
16128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
16138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
1614d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
16158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
16168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
1617d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mSupportedTypes = types;
16188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeMax = maxVolume;
1619d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
1620d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            super.routeUpdated();
1621d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
1622d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
16239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void updateName() {
16249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final StringBuilder sb = new StringBuilder();
16259a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final int count = mRoutes.size();
16269a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
16279a1de308cea2d160778fd977825f10a07b49d738Adam Powell                final RouteInfo info = mRoutes.get(i);
1628b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // TODO: There's probably a much more correct way to localize this.
16299a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (i > 0) sb.append(", ");
16309a1de308cea2d160778fd977825f10a07b49d738Adam Powell                sb.append(info.mName);
16319a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
16329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = sb.toString();
16339a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = false;
16349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1635d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
1636d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1637d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        public String toString() {
1638d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            StringBuilder sb = new StringBuilder(super.toString());
1639d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append('[');
1640d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1641d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
1642d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                if (i > 0) sb.append(", ");
1643d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                sb.append(mRoutes.get(i));
1644d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
1645d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append(']');
1646d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return sb.toString();
1647d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
16489a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
16499a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
16519a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Definition of a category of routes. All routes belong to a category.
16529a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1653b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteCategory {
16549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
16550d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
16569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mTypes;
16579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final boolean mGroupable;
1658705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean mIsSystem;
16599a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16609a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteCategory(CharSequence name, int types, boolean groupable) {
16619a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
16629a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mTypes = types;
16639a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroupable = groupable;
16649a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16659a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16660d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        RouteCategory(int nameResId, int types, boolean groupable) {
16670d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = nameResId;
16680d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mTypes = types;
16690d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mGroupable = groupable;
16700d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
16710d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
16729a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
16739a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the name of this route category
16749a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
16759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
16760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
16770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
16780d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
16790d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
16800d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/configuration dependent name of this RouteCategory.
16810d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
16820d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context to resolve name resources
16830d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return the name of this route category
16840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
16850d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
16860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
16870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
16880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
16890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
16900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
16910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return res.getText(mNameResId);
16920d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
16939a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
16949a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16959a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16969a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
1697d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the current list of routes in this category that have been added
1698d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * to the MediaRouter.
16999a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1700d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * <p>This list will not include routes that are nested within RouteGroups.
1701d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * A RouteGroup is treated as a single route within its category.</p>
1702d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1703d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param out a List to fill with the routes in this category. If this parameter is
1704d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            non-null, it will be cleared, filled with the current routes with this
1705d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            category, and returned. If this parameter is null, a new List will be
1706d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            allocated to report the category's current routes.
1707d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return A list with the routes in this category that have been added to the MediaRouter.
17089a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
1709d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public List<RouteInfo> getRoutes(List<RouteInfo> out) {
1710d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if (out == null) {
1711d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out = new ArrayList<RouteInfo>();
1712d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            } else {
1713d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out.clear();
1714d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1715d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1716b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = getRouteCountStatic();
1717d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            for (int i = 0; i < count; i++) {
1718b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteInfo route = getRouteAtStatic(i);
1719d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                if (route.mCategory == this) {
1720d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                    out.add(route);
1721d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                }
1722d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1723d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return out;
17249a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17259a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
17279a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return Flag set describing the route types supported by this category
17289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
17299a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
17309a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mTypes;
17319a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17329a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
17349a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Return whether or not this category supports grouping.
17359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
17369a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>If this method returns true, all routes obtained from this category
1737d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
17389a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
17399a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return true if this category supports
17409a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
17419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public boolean isGroupable() {
17429a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroupable;
17439a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17449a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1745705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
1746705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @return true if this is the category reserved for system routes.
1747705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @hide
1748705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
1749705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public boolean isSystem() {
1750705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return mIsSystem;
1751705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1752705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
17539a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
17549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
1755d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " groupable=" + mGroupable + " }";
17569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17579a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
17589a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static class CallbackInfo {
17609a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int type;
1761b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final Callback cb;
1762b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final MediaRouter router;
17639a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1764b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public CallbackInfo(Callback cb, int type, MediaRouter router) {
17659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.cb = cb;
17669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.type = type;
1767b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            this.router = router;
17689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17699a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
17709a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
17729a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Interface for receiving events about media routing changes.
17739a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * All methods of this interface will be called from the application's main thread.
17749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
17759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A Callback will only receive events relevant to routes that the callback
17769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * was registered for.</p>
17779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
17789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#addCallback(int, Callback)
17799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#removeCallback(Callback)
17809a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
17810d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static abstract class Callback {
17829a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
17839a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes selected as the active route
17849a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
17859a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1786d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
17879a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been selected
17889a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been selected for the given route types
17899a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
17900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
17919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
17939a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes unselected as the active route
17949a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
17959a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1796d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
17979a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been unselected
17989a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been unselected for the given route types
17999a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
18019a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18039a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was added.
18049a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1805d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
18069a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has become available for use
18079a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18080d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
18099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18119a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was removed.
18129a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1813d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
18149a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been removed from availability
18159a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18160d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
18179a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18189a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18199a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when an aspect of the indicated route has changed.
18209a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
18219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>This will not indicate that the types supported by this route have
18229a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * changed, only that cosmetic info such as name or status have been updated.</p>
18239a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1824d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
18259a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info The route that was changed
18269a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18270d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
1828d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1829d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1830d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is added to a group.
1831d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1832d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1833d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was added
1834d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was added to
1835d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index The route index within group that info was added at
1836d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
18370d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
18380d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                int index);
1839d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1840d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1841d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is removed from a group.
1842d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1843d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1844d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was removed
1845d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was removed from
1846d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
18470d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
18488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
18498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
18508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Called when a route's volume changes.
18518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
18528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param router the MediaRouter reporting the event
18538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param info The route with altered volume
18548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
18558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
18569a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
18579a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18589a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
18590d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Stub implementation of {@link MediaRouter.Callback}.
18600d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Each abstract method is defined as a no-op. Override just the ones
18619a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * you need.
18629a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
18630d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static class SimpleCallback extends Callback {
18649a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1866d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
18679a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18689a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1870d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
18719a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18739a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1874d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteAdded(MediaRouter router, RouteInfo info) {
18759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18769a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1878d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
18799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18809a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1882d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteChanged(MediaRouter router, RouteInfo info) {
18839a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18849a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18859a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1886d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
1887d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                int index) {
18889a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18899a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18909a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1891d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
18929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1893d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
18948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
18958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
18968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
18979a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
18981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
18991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    static class VolumeCallbackInfo {
19001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final VolumeCallback vcb;
19011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final RouteInfo route;
19021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
19031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public VolumeCallbackInfo(VolumeCallback vcb, RouteInfo route) {
19041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.vcb = vcb;
19051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.route = route;
19061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
19071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
19081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
19091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    /**
19101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * Interface for receiving events about volume changes.
19111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * All methods of this interface will be called from the application's main thread.
19121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
19131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * <p>A VolumeCallback will only receive events relevant to routes that the callback
19141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * was registered for.</p>
19151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
19161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * @see UserRouteInfo#setVolumeCallback(VolumeCallback)
19171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     */
19181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    public static abstract class VolumeCallback {
19191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
19201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be increased or decreased.
19211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
19221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param direction an integer indicating whether the volume is to be increased
19231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     (positive value) or decreased (negative value).
19241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     For bundled changes, the absolute value indicates the number of changes
19251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     in the same direction, e.g. +3 corresponds to three "volume up" changes.
19261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
19271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeUpdateRequest(RouteInfo info, int direction);
19281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
19291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be set to the given value
19301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
19311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume an integer indicating the new volume value that should be used, always
19321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     between 0 and the value set by {@link UserRouteInfo#setVolumeMax(int)}.
19331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
19341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeSetRequest(RouteInfo info, int volume);
19351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
19361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
19378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static class VolumeChangeReceiver extends BroadcastReceiver {
19388e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
19398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onReceive(Context context, Intent intent) {
19408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
19418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
19428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        -1);
19438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (streamType != AudioManager.STREAM_MUSIC) {
19448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
19458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
19468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
19478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
19488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int oldVolume = intent.getIntExtra(
19498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
19508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (newVolume != oldVolume) {
19518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    systemVolumeChanged(newVolume);
19528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
19538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
19548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
1955705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
19568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
1957705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
1958705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        @Override
1959705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public void onReceive(Context context, Intent intent) {
1960705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
1961705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
1962705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
1963705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
1964705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
19658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
19669a1de308cea2d160778fd977825f10a07b49d738Adam Powell}
1967