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;
359a1de308cea2d160778fd977825f10a07b49d738Adam Powell
369a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.ArrayList;
379a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.HashMap;
38d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powellimport java.util.List;
3939d5c6172503620ac3761148adac5fd7fa20d02dAdam Powellimport java.util.concurrent.CopyOnWriteArrayList;
409a1de308cea2d160778fd977825f10a07b49d738Adam Powell
419a1de308cea2d160778fd977825f10a07b49d738Adam Powell/**
429a1de308cea2d160778fd977825f10a07b49d738Adam Powell * MediaRouter allows applications to control the routing of media channels
439a1de308cea2d160778fd977825f10a07b49d738Adam Powell * and streams from the current device to external speakers and destination devices.
449a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
45b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
46b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
47b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.MEDIA_ROUTER_SERVICE}.
48b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn *
49b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>The media router API is not thread-safe; all interactions with it must be
50b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * done from the main thread of the process.</p>
519a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
529a1de308cea2d160778fd977825f10a07b49d738Adam Powellpublic class MediaRouter {
539a1de308cea2d160778fd977825f10a07b49d738Adam Powell    private static final String TAG = "MediaRouter";
549a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    static class Static implements DisplayManager.DisplayListener {
56b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Resources mResources;
57632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioService mAudioService;
58705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final DisplayManager mDisplayService;
59b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Handler mHandler;
6039d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
6139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell                new CopyOnWriteArrayList<CallbackInfo>();
62b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
63b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
64b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
65b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
66b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final RouteCategory mSystemCategory;
67632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
68705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
69b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
70705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        RouteInfo mDefaultAudioVideo;
71b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mBluetoothA2dpRoute;
72b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
73b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mSelectedRoute;
749a1de308cea2d160778fd977825f10a07b49d738Adam Powell
75705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplayStatus mLastKnownWifiDisplayStatus;
76705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
77705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
78632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
79632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mHandler.post(new Runnable() {
80632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    @Override public void run() {
81705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        updateAudioRoutes(newRoutes);
82632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
83632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                });
84632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
85632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        };
86632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
87b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        Static(Context appContext) {
88b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mResources = Resources.getSystem();
89b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mHandler = new Handler(appContext.getMainLooper());
909a1de308cea2d160778fd977825f10a07b49d738Adam Powell
91632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
92632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            mAudioService = IAudioService.Stub.asInterface(b);
939a1de308cea2d160778fd977825f10a07b49d738Adam Powell
94705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
95705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
96dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            mSystemCategory = new RouteCategory(
97dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                    com.android.internal.R.string.default_audio_route_category_name,
98705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
99705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mSystemCategory.mIsSystem = true;
100b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        }
101b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
102b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        // Called after sStatic is initialized
1038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        void startMonitoringRoutes(Context appContext) {
104705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo = new RouteInfo(mSystemCategory);
105705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
106705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
10792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute(
10892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    mDefaultAudioVideo, getAllPresentationDisplays());
1092ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            addRouteStatic(mDefaultAudioVideo);
110632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
1112ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // This will select the active wifi display route if there is one.
1122ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
1132ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell
1142ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
1152ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                    new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
1168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            appContext.registerReceiver(new VolumeChangeReceiver(),
1178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
1188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
11992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            mDisplayService.registerDisplayListener(this, mHandler);
12092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
121705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            AudioRoutesInfo newAudioRoutes = null;
122632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            try {
123705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
124632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            } catch (RemoteException e) {
125632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
126705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newAudioRoutes != null) {
1272ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // This will select the active BT route if there is one and the current
1282ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // selected route is the default system route, or if there is no selected
1292ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // route yet.
130705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateAudioRoutes(newAudioRoutes);
131632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
132705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1332ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // Select the default route if the above didn't sync us up
1342ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // appropriately with relevant system state.
1352ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            if (mSelectedRoute == null) {
1362ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo);
1372ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            }
138632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        }
139632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
140705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        void updateAudioRoutes(AudioRoutesInfo newRoutes) {
141705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
142705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
143632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                int name;
144632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
145632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
146632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_headphones;
147632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
148632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
149632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
1504131a37366d59b5e61f55c4e48d2b22ee0c4cad4Adam Powell                    name = com.android.internal.R.string.default_media_route_name_hdmi;
151632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else {
152632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name;
153632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
154705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDefaultAudioVideo.mNameResId = name;
155705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                dispatchRouteChanged(sStatic.mDefaultAudioVideo);
156632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
157bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
1583f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell            final int mainType = mCurAudioRoutesInfo.mMainType;
1593f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell
160bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            boolean a2dpEnabled;
161bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            try {
162bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                a2dpEnabled = mAudioService.isBluetoothA2dpOn();
163bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            } catch (RemoteException e) {
164bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                Log.e(TAG, "Error querying Bluetooth A2DP state", e);
165bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                a2dpEnabled = false;
166bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
167bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
168705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
169705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
170705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                if (mCurAudioRoutesInfo.mBluetoothName != null) {
171632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    if (sStatic.mBluetoothA2dpRoute == null) {
172632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
173705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        info.mName = mCurAudioRoutesInfo.mBluetoothName;
174632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
175632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        sStatic.mBluetoothA2dpRoute = info;
1762ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                        addRouteStatic(sStatic.mBluetoothA2dpRoute);
177632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    } else {
178705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
179632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
180632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
181632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if (sStatic.mBluetoothA2dpRoute != null) {
182632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    removeRoute(sStatic.mBluetoothA2dpRoute);
183632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    sStatic.mBluetoothA2dpRoute = null;
184632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
185632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
186bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
187bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            if (mBluetoothA2dpRoute != null) {
1883f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell                if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
1893f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell                        mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
190705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
1913f369684e13dfea0ba8ea134f3e95930b0dd7df0Adam Powell                } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
1922ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                        a2dpEnabled) {
193bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
194bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                }
195bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
196b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        }
19792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
19892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        @Override
19992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onDisplayAdded(int displayId) {
20092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            updatePresentationDisplays(displayId);
20192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
20292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
20392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        @Override
20492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onDisplayChanged(int displayId) {
20592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            updatePresentationDisplays(displayId);
20692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
20792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
20892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        @Override
20992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onDisplayRemoved(int displayId) {
21092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            updatePresentationDisplays(displayId);
21192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
21292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
21392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public Display[] getAllPresentationDisplays() {
21492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
21592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
21692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
21792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        private void updatePresentationDisplays(int changedDisplayId) {
21892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            final Display[] displays = getAllPresentationDisplays();
21992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            final int count = mRoutes.size();
22092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            for (int i = 0; i < count; i++) {
22192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                final RouteInfo info = mRoutes.get(i);
22292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                Display display = choosePresentationDisplayForRoute(info, displays);
22392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                if (display != info.mPresentationDisplay
22492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                        || (display != null && display.getDisplayId() == changedDisplayId)) {
22592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    info.mPresentationDisplay = display;
22692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    dispatchRoutePresentationDisplayChanged(info);
22792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                }
22892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            }
22992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
230b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
2319a1de308cea2d160778fd977825f10a07b49d738Adam Powell
232b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static Static sStatic;
2339a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2349a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
2359a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for live audio.
2369a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2379a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A device that supports live audio routing will allow the media audio stream
2389a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to be routed to supported destinations. This can include internal speakers or
2399a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * audio jacks on the device itself, A2DP devices, and more.</p>
2409a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2419a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Once initiated this routing is transparent to the application. All audio
2429a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * played on the media stream will be routed to the selected destination.</p>
2439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2449a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
2459a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2469a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
247705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Route type flag for live video.
248705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     *
249705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * <p>A device that supports live video routing will allow a mirrored version
250705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * of the device's primary display or a customized
251705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
252705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     *
253705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * <p>Once initiated, display mirroring is transparent to the application.
254705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * While remote routing is active the application may use a
255705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * {@link android.app.Presentation Presentation} to replace the mirrored view
256705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * on the external display with different content.</p>
25792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown     *
25892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown     * @see RouteInfo#getPresentationDisplay()
25992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown     * @see android.app.Presentation
260705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     */
261705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
262705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
263705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    /**
2649a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for application-specific usage.
2659a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Unlike other media route types, user routes are managed by the application.
2679a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * The MediaRouter will manage and dispatch events for user routes, but the application
2689a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * is expected to interpret the meaning of these events and perform the requested
2699a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * routing tasks.</p>
2709a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_USER = 0x00800000;
2729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    // Maps application contexts
2749a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
2759a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2769a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static String typesToString(int types) {
2779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final StringBuilder result = new StringBuilder();
2789a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
2799a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_LIVE_AUDIO ");
2809a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2812bb7c122ef6ce8539dfbaeb3292adcd942185f82Bryan Mawhinney        if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
2822bb7c122ef6ce8539dfbaeb3292adcd942185f82Bryan Mawhinney            result.append("ROUTE_TYPE_LIVE_VIDEO ");
2832bb7c122ef6ce8539dfbaeb3292adcd942185f82Bryan Mawhinney        }
2849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_USER) != 0) {
2859a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_USER ");
2869a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2879a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return result.toString();
2889a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2899a1de308cea2d160778fd977825f10a07b49d738Adam Powell
290b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    /** @hide */
291b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public MediaRouter(Context context) {
292b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        synchronized (Static.class) {
293b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic == null) {
2948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final Context appContext = context.getApplicationContext();
2958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic = new Static(appContext);
2968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic.startMonitoringRoutes(appContext);
297b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            }
2989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2999a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
301690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
302690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @hide for use by framework routing UI
303690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
304690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSystemAudioRoute() {
305705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return sStatic.mDefaultAudioVideo;
306690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
307690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
308690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
3094599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     * @hide for use by framework routing UI
3104599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     */
3114599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    public RouteCategory getSystemAudioCategory() {
3124599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        return sStatic.mSystemCategory;
3134599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    }
3144599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
3154599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    /**
316690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Return the currently selected route for the given types
317690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
318690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @param type route types
319690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @return the selected route
320690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
321690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSelectedRoute(int type) {
322b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mSelectedRoute;
323690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
324690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
3259a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3269a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add a callback to listen to events about specific kinds of media routes.
3279a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * If the specified callback is already registered, its registration will be updated for any
3289a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * additional route types specified.
3299a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3309a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param types Types of routes this callback is interested in
3319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to add
3329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3339a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addCallback(int types, Callback cb) {
334b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
3359a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
336b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final CallbackInfo info = sStatic.mCallbacks.get(i);
3379a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (info.cb == cb) {
338dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell                info.type |= types;
3399a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
3409a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
3419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
342b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mCallbacks.add(new CallbackInfo(cb, types, this));
3439a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3449a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3459a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3469a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove the specified callback. It will no longer receive events about media routing.
3479a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3489a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to remove
3499a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeCallback(Callback cb) {
351b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
3529a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
353b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic.mCallbacks.get(i).cb == cb) {
354b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCallbacks.remove(i);
3559a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
3569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
3579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
3599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3609a1de308cea2d160778fd977825f10a07b49d738Adam Powell
361d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    /**
362d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * Select the specified route to use for output of the given media types.
363d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *
364d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param types type flags indicating which types this route should be used for.
365d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *              The route must support at least a subset.
366d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param route Route to select
367d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     */
3689a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void selectRoute(int types, RouteInfo route) {
3690d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        // Applications shouldn't programmatically change anything but user routes.
3700d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        types &= ROUTE_TYPE_USER;
3710d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        selectRouteStatic(types, route);
3720d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
3730d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
3740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
3750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @hide internal use
3760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
3770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public void selectRouteInt(int types, RouteInfo route) {
378b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        selectRouteStatic(types, route);
379b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
380b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
381b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void selectRouteStatic(int types, RouteInfo route) {
382705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final RouteInfo oldRoute = sStatic.mSelectedRoute;
383705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (oldRoute == route) return;
3840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        if ((route.getSupportedTypes() & types) == 0) {
3850d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
3860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(route.getSupportedTypes()) + " into route types " +
3870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(types));
3884ee1f55ce0f4909a7430ab44563a81852f335071Adam Powell            return;
3890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
3909a1de308cea2d160778fd977825f10a07b49d738Adam Powell
391dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
392dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
393705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
394dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            try {
395dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
396dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            } catch (RemoteException e) {
397dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                Log.e(TAG, "Error changing Bluetooth A2DP state", e);
398dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            }
399dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        }
400dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell
401705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final WifiDisplay activeDisplay =
402705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
403705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
404705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
405705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
406705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
407705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
408705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            } else if (activeDisplay != null && !newRouteHasAddress) {
409705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.disconnectWifiDisplay();
410705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
411705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
412705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
413705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (oldRoute != null) {
4149a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
415705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
4169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
417b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mSelectedRoute = route;
4189a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (route != null) {
4199a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
4209a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteSelected(types & route.getSupportedTypes(), route);
4219a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
4229a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4239a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4249a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
425705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Compare the device address of a display and a route.
426705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Nulls/no device address will match another null/no address.
427705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     */
428705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
429705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
430705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display == null && !routeHasAddress) {
431705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return true;
432705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
433705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
434705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display != null && routeHasAddress) {
435705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return display.getDeviceAddress().equals(info.mDeviceAddress);
436705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
437705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return false;
438705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
439705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
440705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    /**
4419a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add an app-specified route for media to the MediaRouter.
4429a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
4439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4449a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to add
4459a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createUserRoute()
4469a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
4479a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4489a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addUserRoute(UserRouteInfo info) {
4492ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        addRouteStatic(info);
4509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4519a1de308cea2d160778fd977825f10a07b49d738Adam Powell
452d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
453d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide Framework use only
454d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
455d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void addRouteInt(RouteInfo info) {
4562ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        addRouteStatic(info);
457d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
458d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
4592ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell    static void addRouteStatic(RouteInfo info) {
4609a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory cat = info.getCategory();
461b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (!sStatic.mCategories.contains(cat)) {
462b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mCategories.add(cat);
4639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
464d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        if (cat.isGroupable() && !(info instanceof RouteGroup)) {
4659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // Enforce that any added route in a groupable category must be in a group.
4669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteGroup group = new RouteGroup(info.getCategory());
467dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell            group.mSupportedTypes = info.mSupportedTypes;
468b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(group);
469d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(group);
470b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            group.addRoute(info);
471d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
4729a1de308cea2d160778fd977825f10a07b49d738Adam Powell            info = group;
473d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        } else {
474b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(info);
475d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(info);
4769a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
4779a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4789a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4799a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4809a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove an app-specified route for media from the MediaRouter.
4819a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4829a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to remove
4839a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
4849a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4859a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeUserRoute(UserRouteInfo info) {
4869a1de308cea2d160778fd977825f10a07b49d738Adam Powell        removeRoute(info);
4879a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
489690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
490690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Remove all app-specified routes from the MediaRouter.
491690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
492690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @see #removeUserRoute(UserRouteInfo)
493690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
494690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public void clearUserRoutes() {
495b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        for (int i = 0; i < sStatic.mRoutes.size(); i++) {
496b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.get(i);
497d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // TODO Right now, RouteGroups only ever contain user routes.
498d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // The code below will need to change if this assumption does.
499d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
500690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                removeRouteAt(i);
501690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                i--;
502690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
503690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
504690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
505690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
506d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
507d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide internal use only
508d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
509d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void removeRouteInt(RouteInfo info) {
510d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        removeRoute(info);
511d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
512d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
513b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void removeRoute(RouteInfo info) {
514b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mRoutes.remove(info)) {
5159a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteCategory removingCat = info.getCategory();
516b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
5179a1de308cea2d160778fd977825f10a07b49d738Adam Powell            boolean found = false;
5189a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
519b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
5209a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (removingCat == cat) {
5219a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    found = true;
5229a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    break;
5239a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
5249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
525d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
526d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
527d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
528705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo);
529d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
5309a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!found) {
531b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
5329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5339a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteRemoved(info);
5349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5359a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
537690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    void removeRouteAt(int routeIndex) {
538b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
539b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
540690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            final RouteCategory removingCat = info.getCategory();
541b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
542690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            boolean found = false;
543690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            for (int i = 0; i < count; i++) {
544b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
545690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                if (removingCat == cat) {
546690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    found = true;
547690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    break;
548690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                }
549690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
550d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
551d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
552d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
553705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
554705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        sStatic.mDefaultAudioVideo);
555d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
556690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            if (!found) {
557b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
558690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
559690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            dispatchRouteRemoved(info);
560690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
561690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
562690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
5639a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5649a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteCategory categories} currently
5659a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * represented by routes known to this MediaRouter.
5669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5679a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of unique categories represented by this MediaRouter's known routes
5689a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5699a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getCategoryCount() {
570b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.size();
5719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the {@link MediaRouter.RouteCategory category} at the given index.
5759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Valid indices are in the range [0-getCategoryCount).
5769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index which category to return
5789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the category at index
5799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5809a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory getCategoryAt(int index) {
581b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.get(index);
5829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5849a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteInfo routes} currently known
5869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to this MediaRouter.
5879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of routes tracked by this router
5899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5909a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getRouteCount() {
591b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
5929a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5939a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5949a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5959a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the route at the specified index.
5969a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5979a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index index of the route to return
5989a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the route at index
5999a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
6009a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteInfo getRouteAt(int index) {
601b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
602b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
603b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
604b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static int getRouteCountStatic() {
605b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
606b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
607b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
608b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static RouteInfo getRouteAtStatic(int index) {
609b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
6109a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6119a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6129a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
6139a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new user route that may be modified and registered for use by the application.
6149a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
6159a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param category The category the new route will belong to
6169a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return A new UserRouteInfo for use by the application
6179a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
6189a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
6199a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
6209a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createRouteCategory(CharSequence)
6219a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
6229a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public UserRouteInfo createUserRoute(RouteCategory category) {
6239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new UserRouteInfo(category);
6249a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6259a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6269a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
6279a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new route category. Each route must belong to a category.
6289a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
6299a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param name Name of the new category
6309a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
6319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the new RouteCategory
6329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
6339a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
6349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
6359a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6360d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
6370d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
6380d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Create a new route category. Each route must belong to a category.
6390d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     *
6400d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param nameResId Resource ID of the name of the new category
6410d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
6420d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @return the new RouteCategory
6430d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
6440d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
6450d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
6460d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
6479a1de308cea2d160778fd977825f10a07b49d738Adam Powell
648b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void updateRoute(final RouteInfo info) {
6499a1de308cea2d160778fd977825f10a07b49d738Adam Powell        dispatchRouteChanged(info);
6509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6519a1de308cea2d160778fd977825f10a07b49d738Adam Powell
652b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteSelected(int type, RouteInfo info) {
65339d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
655b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteSelected(cbi.router, type, info);
6569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6589a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6599a1de308cea2d160778fd977825f10a07b49d738Adam Powell
660b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUnselected(int type, RouteInfo info) {
66139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6629a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
663b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUnselected(cbi.router, type, info);
6649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6669a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6679a1de308cea2d160778fd977825f10a07b49d738Adam Powell
668b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteChanged(RouteInfo info) {
66939d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6709a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
671b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteChanged(cbi.router, info);
6729a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6739a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6749a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6759a1de308cea2d160778fd977825f10a07b49d738Adam Powell
676b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteAdded(RouteInfo info) {
67739d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
679b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteAdded(cbi.router, info);
6809a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
684b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteRemoved(RouteInfo info) {
68539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
6869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
687b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteRemoved(cbi.router, info);
6889a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
6899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6909a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
6919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
692b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
69339d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
694d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
695b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteGrouped(cbi.router, info, group, index);
696d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
697d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
698d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    }
699d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
700b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
70139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
702d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
703b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUngrouped(cbi.router, info, group);
7049a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
7059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7069a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
7079a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void dispatchRouteVolumeChanged(RouteInfo info) {
7098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
7108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
7118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                cbi.cb.onRouteVolumeChanged(cbi.router, info);
7128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
7138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
7148e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
7158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
71692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    static void dispatchRoutePresentationDisplayChanged(RouteInfo info) {
71792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        for (CallbackInfo cbi : sStatic.mCallbacks) {
71892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            if ((cbi.type & info.mSupportedTypes) != 0) {
71992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info);
72092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            }
72192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
72292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    }
72392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
7248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void systemVolumeChanged(int newValue) {
7258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        final RouteInfo selectedRoute = sStatic.mSelectedRoute;
7268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == null) return;
7278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
7288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
729705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectedRoute == sStatic.mDefaultAudioVideo) {
7308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(selectedRoute);
7318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else if (sStatic.mBluetoothA2dpRoute != null) {
7328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            try {
7338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
734705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
7358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } catch (RemoteException e) {
7368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
7378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
7388e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else {
739705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
740705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
741705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
742705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
743705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
744705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
745705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
746705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        // TODO Naive implementation. Make this smarter later.
747b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell        boolean wantScan = false;
748b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell        boolean blockScan = false;
749705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplay[] oldDisplays = oldStatus != null ?
750705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                oldStatus.getRememberedDisplays() : new WifiDisplay[0];
751705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplay[] newDisplays = newStatus.getRememberedDisplays();
752705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays();
7532ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        WifiDisplay activeDisplay = newStatus.getActiveDisplay();
754705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
755705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < newDisplays.length; i++) {
756705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay d = newDisplays[i];
757705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays);
758705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (oldRemembered == null) {
75911b999d0aaca72a33526795b6849b473cc3dd569Adam Powell                addRouteStatic(makeWifiDisplayRoute(d,
76011b999d0aaca72a33526795b6849b473cc3dd569Adam Powell                        findMatchingDisplay(d, availableDisplays) != null));
761b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                wantScan = true;
762705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            } else {
763705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                final boolean available = findMatchingDisplay(d, availableDisplays) != null;
764705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                final RouteInfo route = findWifiDisplayRoute(d);
765705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateWifiDisplayRoute(route, d, available, newStatus);
766705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
7672ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            if (d.equals(activeDisplay)) {
7682ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                final RouteInfo activeRoute = findWifiDisplayRoute(d);
7692ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                if (activeRoute != null) {
7702ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                    selectRouteStatic(activeRoute.getSupportedTypes(), activeRoute);
771b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell
772b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                    // Don't scan if we're already connected to a wifi display,
773b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                    // the scanning process can cause a hiccup with some configurations.
774b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell                    blockScan = true;
7752ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                }
7762ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            }
777705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
778705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < oldDisplays.length; i++) {
779705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay d = oldDisplays[i];
780705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
781705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newDisplay == null) {
782705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                removeRoute(findWifiDisplayRoute(d));
783705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
784705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
785705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
786b072a9686c29bfbc05b732076a4e89bcca8db08aAdam Powell        if (wantScan && !blockScan) {
787705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            sStatic.mDisplayService.scanWifiDisplays();
788705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
789705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
790705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        sStatic.mLastKnownWifiDisplayStatus = newStatus;
791705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
792705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
79311b999d0aaca72a33526795b6849b473cc3dd569Adam Powell    static RouteInfo makeWifiDisplayRoute(WifiDisplay display, boolean available) {
794705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
795705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mDeviceAddress = display.getDeviceAddress();
796705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
797705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
798705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
79911b999d0aaca72a33526795b6849b473cc3dd569Adam Powell
80011b999d0aaca72a33526795b6849b473cc3dd569Adam Powell        newRoute.setStatusCode(available ?
80111b999d0aaca72a33526795b6849b473cc3dd569Adam Powell                RouteInfo.STATUS_AVAILABLE : RouteInfo.STATUS_CONNECTING);
80211b999d0aaca72a33526795b6849b473cc3dd569Adam Powell        newRoute.mEnabled = available;
803705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
8042444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        newRoute.mName = display.getFriendlyDisplayName();
80592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
80692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute,
80792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                sStatic.getAllPresentationDisplays());
808705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return newRoute;
809705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
810705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
811705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display,
812705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            boolean available, WifiDisplayStatus wifiDisplayStatus) {
813705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean isScanning =
814705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING;
815705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
816705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean changed = false;
817705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        int newStatus = RouteInfo.STATUS_NONE;
818705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
819705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (available) {
820705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE;
821705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        } else {
822705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
823705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
824705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
825705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display.equals(wifiDisplayStatus.getActiveDisplay())) {
826705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final int activeState = wifiDisplayStatus.getActiveDisplayState();
827705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            switch (activeState) {
828705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
829705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    newStatus = RouteInfo.STATUS_NONE;
830705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
831705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
832705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    newStatus = RouteInfo.STATUS_CONNECTING;
833705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
834705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
835705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    Log.e(TAG, "Active display is not connected!");
836705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
837705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
838705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
839705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
8402444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        final String newName = display.getFriendlyDisplayName();
8412444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        if (!route.getName().equals(newName)) {
842705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            route.mName = newName;
843705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            changed = true;
844705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
845705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
846705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        changed |= route.mEnabled != available;
847705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        route.mEnabled = available;
848705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
849705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        changed |= route.setStatusCode(newStatus);
850705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
851705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (changed) {
852705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteChanged(route);
853705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
854705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
855705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (!available && route == sStatic.mSelectedRoute) {
856705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            // Oops, no longer available. Reselect the default.
857705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
858705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
859705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
860705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
861705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
8622444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown    private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) {
863705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < displays.length; i++) {
8642444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown            final WifiDisplay other = displays[i];
8652444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown            if (d.getDeviceAddress().equals(other.getDeviceAddress())) {
8662444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown                return other;
867705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
868705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
869705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return null;
870705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
871705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
872705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
873705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final int count = sStatic.mRoutes.size();
874705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < count; i++) {
875705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final RouteInfo info = sStatic.mRoutes.get(i);
876705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
877705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                return info;
878705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
879705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
880705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return null;
8818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
8828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
88392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) {
88492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
88592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            if (route.mDeviceAddress != null) {
88692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                // Find the indicated Wifi display by its address.
88792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                for (Display display : displays) {
88892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    if (display.getType() == Display.TYPE_WIFI
88992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                            && route.mDeviceAddress.equals(display.getAddress())) {
89092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                        return display;
89192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    }
89292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                }
89392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                return null;
89492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            }
89592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
89692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            if (route == sStatic.mDefaultAudioVideo && displays.length > 0) {
89792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                // Choose the first presentation display from the list.
89892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                return displays[0];
89992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            }
90092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
90192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        return null;
90292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    }
90392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
9049a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
9059a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a media route.
9069a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
907b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteInfo {
9089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
9090d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
9109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private CharSequence mStatus;
9119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mSupportedTypes;
9129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup mGroup;
9139a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory mCategory;
914ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        Drawable mIcon;
9151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        // playback information
9161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackType = PLAYBACK_TYPE_LOCAL;
9171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
9181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
9191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
9201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackStream = AudioManager.STREAM_MUSIC;
9211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        VolumeCallbackInfo mVcb;
92292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        Display mPresentationDisplay;
9239a1de308cea2d160778fd977825f10a07b49d738Adam Powell
924705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        String mDeviceAddress;
925705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean mEnabled = true;
926705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
927705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        // A predetermined connection status that can override mStatus
928705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        private int mStatusCode;
929705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
9302ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_NONE = 0;
9312ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_SCANNING = 1;
9322ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_CONNECTING = 2;
9332ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_AVAILABLE = 3;
9342ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
935705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
936b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        private Object mTag;
937b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
9381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
9391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * The default playback type, "local", indicating the presentation of the media is happening
9401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * on the same device (e.g. a phone, a tablet) as where it is controlled from.
9411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
9421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
9431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_LOCAL = 0;
9441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
9451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * A playback type indicating the presentation of the media is happening on
9461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * a different device (i.e. the remote device) than where it is controlled from.
9471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
9481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
9491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_REMOTE = 1;
9501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
9511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is fixed, i.e. it cannot be
9521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * controlled from this object. An example of fixed playback volume is a remote player,
9531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
9541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * than attenuate at the source.
9551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setVolumeHandling(int)
9561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
9571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_FIXED = 0;
9581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
9591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is variable and can be controlled
9601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * from this object.
9611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
9621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_VARIABLE = 1;
9631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
9649a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteInfo(RouteCategory category) {
9659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mCategory = category;
9669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9679a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9699a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly name of a media route. This is the string presented
9709a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * to users who may select this as the active route.
9719a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9729a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
9730d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
9740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
9760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
9770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/resource selected name of this route.
9780d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
9790d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context used to resolve the correct configuration to load
9800d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return The user-friendly name of the media route. This is the string presented
9810d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * to users who may select this as the active route.
9820d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
9830d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
9840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
9850d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
9870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
9880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
9890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return mName = res.getText(mNameResId);
9900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
9919a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
9929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9939a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9949a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9959a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly status for a media route. This may include a description
9969a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of the currently playing media, if available.
9979a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getStatus() {
9999a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mStatus;
10009a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
10019a1de308cea2d160778fd977825f10a07b49d738Adam Powell
10029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
1003705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * Set this route's status by predetermined status code. If the caller
1004705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * should dispatch a route changed event this call will return true;
1005705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
1006705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean setStatusCode(int statusCode) {
1007705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (statusCode != mStatusCode) {
1008705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mStatusCode = statusCode;
1009705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                int resId = 0;
1010705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                switch (statusCode) {
1011705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_SCANNING:
1012705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_scanning;
1013705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
1014705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_CONNECTING:
1015705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_connecting;
1016705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
1017705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_AVAILABLE:
1018705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_available;
1019705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
1020705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_NOT_AVAILABLE:
1021705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        resId = com.android.internal.R.string.media_route_status_not_available;
1022705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        break;
1023705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                }
1024705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
1025705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                return true;
1026705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
1027705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return false;
1028705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1029705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1030705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
10312ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell         * @hide
10322ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell         */
10332ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        public int getStatusCode() {
10342ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            return mStatusCode;
10352ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        }
10362ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell
10372ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /**
10389a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return A media type flag set describing which types this route supports.
10399a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
10409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
10419a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mSupportedTypes;
10429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
10439a1de308cea2d160778fd977825f10a07b49d738Adam Powell
10449a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
10459a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The group that this route belongs to.
10469a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
10479a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteGroup getGroup() {
10489a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroup;
10499a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
10509a1de308cea2d160778fd977825f10a07b49d738Adam Powell
10519a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
10529a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the category this route belongs to.
10539a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
10549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteCategory getCategory() {
10559a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mCategory;
10569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
10579a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1058ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1059ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Get the icon representing this route.
1060ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * This icon will be used in picker UIs if available.
1061ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1062ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @return the icon representing this route or null if no icon is available
1063ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1064ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public Drawable getIconDrawable() {
1065ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            return mIcon;
1066ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1067ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1068b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
1069b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * Set an application-specific tag object for this route.
1070b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * The application may use this to store arbitrary data associated with the
1071b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * route for internal tracking.
1072b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
1073b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * <p>Note that the lifespan of a route may be well past the lifespan of
1074b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * an Activity or other Context; take care that objects you store here
1075b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * will not keep more data in memory alive than you intend.</p>
1076b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
1077b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @param tag Arbitrary, app-specific data for this route to hold for later use
1078b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
1079b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public void setTag(Object tag) {
1080b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            mTag = tag;
1081130b4572d1f3df702e5b296a655d15a41f6d4c66Adam Powell            routeUpdated();
1082b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
1083b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
1084b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
1085b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @return The tag object previously set by the application
1086b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @see #setTag(Object)
1087b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
1088b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public Object getTag() {
1089b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            return mTag;
1090b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
1091b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
10921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the type of playback associated with this route
10941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackType(int)
10951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackType() {
10971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackType;
10981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
11011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the stream over which the playback associated with this route is performed
11021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackStream(int)
11031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
11041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackStream() {
11051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackStream;
11061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
11098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Return the current volume for this route. Depending on the route, this may only
11108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * be valid if the route is currently selected.
11118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
11121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the volume at which the playback associated with this route is performed
11131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolume(int)
11141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
11151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolume() {
11161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
11171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int vol = 0;
11181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
11191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream);
11201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
11211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
11221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
11231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return vol;
11241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
11251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolume;
11261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
11308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request a volume change for this route.
11318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param volume value between 0 and getVolumeMax
11328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
11338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
11348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
11358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
11368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
11378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
11388e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
11398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
11408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
11418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
11428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
11438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
11448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
11458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
11468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
11478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
11488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request an incremental volume update for this route.
11498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param direction Delta to apply to the current volume
11508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
11518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
11528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
11538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
11548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    final int volume =
11558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                            Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
11568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
11578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
11588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
11598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
11608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
11618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
11628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
11638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
11648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
11658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
11668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
11678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
11681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the maximum volume at which the playback associated with this route is performed
11691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeMax(int)
11701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
11711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeMax() {
11721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
11731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int volMax = 0;
11741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
11751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    volMax = sStatic.mAudioService.getStreamMaxVolume(mPlaybackStream);
11761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
11771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
11781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
11791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return volMax;
11801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
11811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolumeMax;
11821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
11861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return how volume is handling on the route
11871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeHandling(int)
11881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
11891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeHandling() {
11901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mVolumeHandling;
11911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
1193705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
119492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * Gets the {@link Display} that should be used by the application to show
119592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * a {@link android.app.Presentation} on an external display when this route is selected.
119692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * Depending on the route, this may only be valid if the route is currently
119792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * selected.
119892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * <p>
119992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * The preferred presentation display may change independently of the route
120092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * being selected or unselected.  For example, the presentation display
120192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * of the default system route may change when an external HDMI display is connected
120292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * or disconnected even though the route itself has not changed.
120392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p><p>
120492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * This method may return null if there is no external display associated with
120592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * the route or if the display is not ready to show UI yet.
120692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p><p>
120792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * The application should listen for changes to the presentation display
120892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
120992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * show or dismiss its {@link android.app.Presentation} accordingly when the display
121092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * becomes available or is removed.
121192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p><p>
121292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * This method only makes sense for {@link #ROUTE_TYPE_LIVE_VIDEO live video} routes.
121392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p>
121492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
121592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @return The preferred presentation display to use when this route is
121692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * selected or null if none.
121792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
121892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @see #ROUTE_TYPE_LIVE_VIDEO
121992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @see android.app.Presentation
122092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         */
122192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public Display getPresentationDisplay() {
122292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            return mPresentationDisplay;
122392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
122492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
122592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        /**
1226705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @return true if this route is enabled and may be selected
1227705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
1228705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public boolean isEnabled() {
1229705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return mEnabled;
1230705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1231705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
12329a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void setStatusInt(CharSequence status) {
12339a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!status.equals(mStatus)) {
12349a1de308cea2d160778fd977825f10a07b49d738Adam Powell                mStatus = status;
12359a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (mGroup != null) {
12369a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    mGroup.memberStatusChanged(this, status);
12379a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
12389a1de308cea2d160778fd977825f10a07b49d738Adam Powell                routeUpdated();
12399a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
12409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
12431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
12441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                sStatic.mHandler.post(new Runnable() {
12451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    @Override
12461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    public void run() {
12471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        if (mVcb != null) {
12481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            if (direction != 0) {
12491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
12501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            } else {
12511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeSetRequest(mVcb.route, value);
12521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            }
12531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        }
12541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    }
12551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                });
12561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
12571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        };
12581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
12599a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void routeUpdated() {
12609a1de308cea2d160778fd977825f10a07b49d738Adam Powell            updateRoute(this);
12619a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12629a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
12649a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
1265d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            String supportedTypes = typesToString(getSupportedTypes());
126692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            return getClass().getSimpleName() + "{ name=" + getName() +
126792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", status=" + getStatus() +
126892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", category=" + getCategory() +
126992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", supportedTypes=" + supportedTypes +
127092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", presentationDisplay=" + mPresentationDisplay + "}";
12719a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12729a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12739a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12749a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
12759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that the application may define and modify.
12768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
12778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
12789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
12799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter.RouteInfo
12809a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1281b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class UserRouteInfo extends RouteInfo {
1282ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        RemoteControlClient mRcc;
12839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        UserRouteInfo(RouteCategory category) {
12859a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
12869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mSupportedTypes = ROUTE_TYPE_USER;
12878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = PLAYBACK_TYPE_REMOTE;
12888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
12899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12909a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
12929a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the user-visible name of this route.
12939a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param name Name to display to the user to describe this route
12949a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
12959a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setName(CharSequence name) {
12969a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
12979a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
12989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
13000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
13010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Set the user-visible name of this route.
13020d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param resId Resource ID of the name to display to the user to describe this route
13030d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
13040d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public void setName(int resId) {
13050d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = resId;
13060d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mName = null;
13070d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            routeUpdated();
13080d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
13099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
13119a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the current user-visible status for this route.
13129a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param status Status to display to the user to describe what the endpoint
13139a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of this route is currently doing
13149a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
13159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setStatus(CharSequence status) {
13169a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
13179a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1318ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1319ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1320ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set the RemoteControlClient responsible for reporting playback info for this
1321ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * user route.
1322ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1323ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * <p>If this route manages remote playback, the data exposed by this
1324ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * RemoteControlClient will be used to reflect and update information
1325ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * such as route volume info in related UIs.</p>
1326ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
13271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * <p>The RemoteControlClient must have been previously registered with
13281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.</p>
13291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *
1330ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param rcc RemoteControlClient associated with this route
1331ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1332ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setRemoteControlClient(RemoteControlClient rcc) {
1333ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mRcc = rcc;
13341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            updatePlaybackInfoOnRcc();
1335ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1336ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1337ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
13384599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * Retrieve the RemoteControlClient associated with this route, if one has been set.
13394599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         *
13404599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @return the RemoteControlClient associated with this route
13414599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @see #setRemoteControlClient(RemoteControlClient)
13424599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         */
13434599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        public RemoteControlClient getRemoteControlClient() {
13444599696591f745b3a546197d2ba7e5cfc5562484Adam Powell            return mRcc;
13454599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        }
13464599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
13474599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        /**
1348ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
1349ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1350ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1351ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this route
1352ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1353ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
1354ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
1355ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1356ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1357ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1358ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
1359ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1360ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
136171c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this route
1362ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1363ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1364ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1365ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
13661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
13681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Set a callback to be notified of volume update requests
13691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param vcb
13701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
13711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeCallback(VolumeCallback vcb) {
13721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            mVcb = new VolumeCallbackInfo(vcb, this);
13731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
13761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether playback associated with this route is "local"
13771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_LOCAL}) or "remote"
13781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_REMOTE}).
13791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param type
13801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
13811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackType(int type) {
13821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType != type) {
13831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackType = type;
13841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, type);
13851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
13861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
13871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
13881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
13891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether volume for the playback associated with this route is fixed
13901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_FIXED}) or can modified
13911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_VARIABLE}).
13921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeHandling
13931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
13941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeHandling(int volumeHandling) {
13951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeHandling != volumeHandling) {
13961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeHandling = volumeHandling;
13971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(
13981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, volumeHandling);
13991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
14001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
14011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
14021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
14031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines at what volume the playback associated with this route is performed (for user
14041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * feedback purposes). This information is only used when the playback is not local.
14051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume
14061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
14071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolume(int volume) {
14088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            volume = Math.max(0, Math.min(volume, getVolumeMax()));
14091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolume != volume) {
14101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolume = volume;
14111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
14128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
1413f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (mGroup != null) {
1414f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    mGroup.memberVolumeChanged(this);
1415f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
14168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
14178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
14188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
14198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
14208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
14218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
14228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
14238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
14248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
14258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
14268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeSetRequest(this, volume);
14278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
14288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
14298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
14308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
14318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
14328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
14338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
14348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
14358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
14368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
14378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeUpdateRequest(this, direction);
14381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
14391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
14401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
14411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
14421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines the maximum volume at which the playback associated with this route is performed
14431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * (for user feedback purposes). This information is only used when the playback is not
14441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * local.
14451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeMax
14461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
14471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeMax(int volumeMax) {
14481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeMax != volumeMax) {
14491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeMax = volumeMax;
14501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, volumeMax);
14511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
14521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
14531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
14541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
14551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines over what stream type the media is presented.
14561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param stream
14571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
14581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackStream(int stream) {
14591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackStream != stream) {
14601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackStream = stream;
14611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_USES_STREAM, stream);
14621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
14631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
14641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
14651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void updatePlaybackInfoOnRcc() {
14661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if ((mRcc != null) && (mRcc.getRcseId() != RemoteControlClient.RCSE_ID_UNREGISTERED)) {
14671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
14681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, mVolumeMax);
14691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
14701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME, mVolume);
14711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
14721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, mVolumeHandling);
14731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
14741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_USES_STREAM, mPlaybackStream);
14751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
14761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, mPlaybackType);
14771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                // let AudioService know whom to call when remote volume needs to be updated
14781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
14791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    sStatic.mAudioService.registerRemoteVolumeObserverForRcc(
14801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            mRcc.getRcseId() /* rccId */, mRemoteVolObserver /* rvo */);
14811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
14821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error registering remote volume observer", e);
14831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
14841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
14851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
14861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
14871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void setPlaybackInfoOnRcc(int what, int value) {
14881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mRcc != null) {
14891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(what, value);
14901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
14911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
14929a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
14939a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14949a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
14959a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that consists of multiple other routes in a group.
14969a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1497b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteGroup extends RouteInfo {
14989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
14999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private boolean mUpdateName;
15009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup(RouteCategory category) {
15029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
15039a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroup = this;
15048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
15059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15069a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15070d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
15089a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (mUpdateName) updateName();
15090d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return super.getName(res);
15109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15119a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15139a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group. The route must not currently belong to another group.
15149a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
15159a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add to this group
15169a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15179a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route) {
15189a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
15199a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
15209a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
15219a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
15229a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
15239a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
15249a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
15259a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
15269a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
1527d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            final int at = mRoutes.size();
15289a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(route);
1529d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
15309a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1531f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
15329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1533f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, at);
15349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15359a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15369a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15379a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group before the specified index.
15389a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
15399a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add
15409a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param insertAt insert the new route before this index
15419a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route, int insertAt) {
15439a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
15449a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
15459a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
15469a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
15479a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
15489a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
15499a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
15509a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
15519a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
15529a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(insertAt, route);
1553d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
15549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1555f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
15569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1557f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, insertAt);
15589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15599a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15609a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15619a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove a route from this group.
15629a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
15639a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to remove
15649a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(RouteInfo route) {
15669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != this) {
15679a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException("Route " + route +
15689a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        " is not a member of this group.");
15699a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
15709a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.remove(route);
1571d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
15729a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1573f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1574d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
15759a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
15769a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15779a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15789a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15799a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove the route at the specified index from this group.
15809a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
15819a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param index index of the route to remove
15829a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15839a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(int index) {
1584d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            RouteInfo route = mRoutes.remove(index);
1585d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
15869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1587f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1588d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
15899a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
15909a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1592d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1593d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The number of routes in this group
1594d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1595d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public int getRouteCount() {
1596d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.size();
1597d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1598d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1599d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1600d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the route in this group at the specified index
1601d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1602d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index Index to fetch
1603d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The route at index
1604d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1605d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public RouteInfo getRouteAt(int index) {
1606d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.get(index);
1607d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1608d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1609ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1610ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1611ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1612ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1613ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this group
1614ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1615ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
1616ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
1617ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1618ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1619ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1620ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1621ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1622ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
162371c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this group
1624ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1625ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1626ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1627ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1628ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
16298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
16308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
16318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
16328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
16338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
16348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final float scaledVolume = (float) volume / maxVol;
16378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
16388e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
16398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
16408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeVol = (int) (scaledVolume * route.getVolumeMax());
16418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestSetVolume(routeVol);
16428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
16448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
16458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
16468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
16488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
16508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
16518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
16528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
16538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
16548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
1657f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
16588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
16598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
16608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestUpdateVolume(direction);
1661f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = route.getVolume();
1662f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1663f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1664f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
16658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
16678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
16688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
16698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
16718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16729a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberNameChanged(RouteInfo info, CharSequence name) {
16739a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
16749a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
16759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16769a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberStatusChanged(RouteInfo info, CharSequence status) {
16789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
16799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16809a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1681f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void memberVolumeChanged(RouteInfo info) {
1682f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1683f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1684f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1685f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void updateVolume() {
1686f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            // A group always represents the highest component volume value.
1687f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            final int routeCount = getRouteCount();
1688f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
1689f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            for (int i = 0; i < routeCount; i++) {
1690f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = getRouteAt(i).getVolume();
1691f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1692f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1693f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
1694f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1695f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            if (volume != mVolume) {
1696f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                mVolume = volume;
1697f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                dispatchRouteVolumeChanged(this);
1698f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1699f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1700f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1701d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1702d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        void routeUpdated() {
1703d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            int types = 0;
1704d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1705b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            if (count == 0) {
1706b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // Don't keep empty groups in the router.
1707b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                MediaRouter.removeRoute(this);
1708b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                return;
1709b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            }
1710b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
17118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            int maxVolume = 0;
17128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isLocal = true;
17138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isFixedVolume = true;
1714d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
17158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = mRoutes.get(i);
17168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                types |= route.mSupportedTypes;
17178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeMaxVolume = route.getVolumeMax();
17188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (routeMaxVolume > maxVolume) {
17198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    maxVolume = routeMaxVolume;
17208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
17218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
17228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
1723d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
17248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
17258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
1726d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mSupportedTypes = types;
17278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeMax = maxVolume;
1728d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
1729d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            super.routeUpdated();
1730d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
1731d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
17329a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void updateName() {
17339a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final StringBuilder sb = new StringBuilder();
17349a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final int count = mRoutes.size();
17359a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
17369a1de308cea2d160778fd977825f10a07b49d738Adam Powell                final RouteInfo info = mRoutes.get(i);
1737b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // TODO: There's probably a much more correct way to localize this.
17389a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (i > 0) sb.append(", ");
17399a1de308cea2d160778fd977825f10a07b49d738Adam Powell                sb.append(info.mName);
17409a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
17419a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = sb.toString();
17429a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = false;
17439a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1744d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
1745d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1746d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        public String toString() {
1747d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            StringBuilder sb = new StringBuilder(super.toString());
1748d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append('[');
1749d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1750d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
1751d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                if (i > 0) sb.append(", ");
1752d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                sb.append(mRoutes.get(i));
1753d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
1754d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append(']');
1755d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return sb.toString();
1756d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
17579a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
17589a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
17609a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Definition of a category of routes. All routes belong to a category.
17619a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1762b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteCategory {
17639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
17640d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
17659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mTypes;
17669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final boolean mGroupable;
1767705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean mIsSystem;
17689a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteCategory(CharSequence name, int types, boolean groupable) {
17709a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
17719a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mTypes = types;
17729a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroupable = groupable;
17739a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17749a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        RouteCategory(int nameResId, int types, boolean groupable) {
17760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = nameResId;
17770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mTypes = types;
17780d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mGroupable = groupable;
17790d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
17800d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
17819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
17829a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the name of this route category
17839a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
17849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
17850d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
17860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
17870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
17880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
17890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/configuration dependent name of this RouteCategory.
17900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
17910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context to resolve name resources
17920d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return the name of this route category
17930d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
17940d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
17950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
17960d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
17970d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
17980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
17990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
18000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return res.getText(mNameResId);
18010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
18029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
18039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
1806d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the current list of routes in this category that have been added
1807d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * to the MediaRouter.
18089a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1809d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * <p>This list will not include routes that are nested within RouteGroups.
1810d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * A RouteGroup is treated as a single route within its category.</p>
1811d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1812d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param out a List to fill with the routes in this category. If this parameter is
1813d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            non-null, it will be cleared, filled with the current routes with this
1814d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            category, and returned. If this parameter is null, a new List will be
1815d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            allocated to report the category's current routes.
1816d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return A list with the routes in this category that have been added to the MediaRouter.
18179a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
1818d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public List<RouteInfo> getRoutes(List<RouteInfo> out) {
1819d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if (out == null) {
1820d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out = new ArrayList<RouteInfo>();
1821d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            } else {
1822d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out.clear();
1823d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1824d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1825b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = getRouteCountStatic();
1826d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            for (int i = 0; i < count; i++) {
1827b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteInfo route = getRouteAtStatic(i);
1828d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                if (route.mCategory == this) {
1829d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                    out.add(route);
1830d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                }
1831d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1832d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return out;
18339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18349a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18359a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18369a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return Flag set describing the route types supported by this category
18379a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
18399a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mTypes;
18409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18439a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Return whether or not this category supports grouping.
18449a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
18459a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>If this method returns true, all routes obtained from this category
1846d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
18479a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
18489a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return true if this category supports
18499a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18509a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public boolean isGroupable() {
18519a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroupable;
18529a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18539a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1854705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
1855705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @return true if this is the category reserved for system routes.
1856705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @hide
1857705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
1858705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public boolean isSystem() {
1859705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return mIsSystem;
1860705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1861705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
18629a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
18639a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
1864d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " groupable=" + mGroupable + " }";
18659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18669a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
18679a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18689a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static class CallbackInfo {
18699a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int type;
1870b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final Callback cb;
1871b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final MediaRouter router;
18729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1873b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public CallbackInfo(Callback cb, int type, MediaRouter router) {
18749a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.cb = cb;
18759a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.type = type;
1876b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            this.router = router;
18779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18789a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
18799a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18809a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
18819a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Interface for receiving events about media routing changes.
18829a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * All methods of this interface will be called from the application's main thread.
18839a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
18849a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A Callback will only receive events relevant to routes that the callback
18859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * was registered for.</p>
18869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
18879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#addCallback(int, Callback)
18889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#removeCallback(Callback)
18899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
18900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static abstract class Callback {
18919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18929a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes selected as the active route
18939a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
18949a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1895d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
18969a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been selected
18979a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been selected for the given route types
18989a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
19009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
19029a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes unselected as the active route
19039a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
19049a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1905d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
19069a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been unselected
19079a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been unselected for the given route types
19089a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
19090d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
19109a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
19129a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was added.
19139a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1914d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
19159a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has become available for use
19169a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
19170d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
19189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
19209a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was removed.
19219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1922d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
19239a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been removed from availability
19249a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
19250d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
19269a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
19289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when an aspect of the indicated route has changed.
19299a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
19309a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>This will not indicate that the types supported by this route have
19319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * changed, only that cosmetic info such as name or status have been updated.</p>
19329a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1933d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
19349a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info The route that was changed
19359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
19360d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
1937d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1938d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1939d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is added to a group.
1940d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1941d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1942d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was added
1943d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was added to
1944d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index The route index within group that info was added at
1945d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
19460d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
19470d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                int index);
1948d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1949d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1950d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is removed from a group.
1951d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1952d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1953d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was removed
1954d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was removed from
1955d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
19560d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
19578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
19588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
19598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Called when a route's volume changes.
19608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
19618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param router the MediaRouter reporting the event
19628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param info The route with altered volume
19638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
19648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
196592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
196692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        /**
196792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * Called when a route's presentation display changes.
196892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * <p>
196992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * This method is called whenever the route's presentation display becomes
197092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * available, is removes or has changes to some of its properties (such as its size).
197192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p>
197292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
197392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @param router the MediaRouter reporting the event
197492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @param info The route whose presentation display changed
197592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
197692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @see RouteInfo#getPresentationDisplay()
197792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         */
197892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
197992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
19809a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
19819a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
19830d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Stub implementation of {@link MediaRouter.Callback}.
19840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Each abstract method is defined as a no-op. Override just the ones
19859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * you need.
19869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
19870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static class SimpleCallback extends Callback {
19889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1990d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
19919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
19929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19939a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1994d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
19959a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
19969a1de308cea2d160778fd977825f10a07b49d738Adam Powell
19979a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1998d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteAdded(MediaRouter router, RouteInfo info) {
19999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
20009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
20019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2002d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
20039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
20049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
20059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2006d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteChanged(MediaRouter router, RouteInfo info) {
20079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
20089a1de308cea2d160778fd977825f10a07b49d738Adam Powell
20099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2010d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
2011d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                int index) {
20129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
20139a1de308cea2d160778fd977825f10a07b49d738Adam Powell
20149a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2015d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
20169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2017d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
20188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
20198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
20208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
20219a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
20221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
20231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    static class VolumeCallbackInfo {
20241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final VolumeCallback vcb;
20251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final RouteInfo route;
20261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
20271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public VolumeCallbackInfo(VolumeCallback vcb, RouteInfo route) {
20281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.vcb = vcb;
20291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.route = route;
20301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
20311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
20321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
20331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    /**
20341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * Interface for receiving events about volume changes.
20351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * All methods of this interface will be called from the application's main thread.
20361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
20371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * <p>A VolumeCallback will only receive events relevant to routes that the callback
20381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * was registered for.</p>
20391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
20401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * @see UserRouteInfo#setVolumeCallback(VolumeCallback)
20411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     */
20421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    public static abstract class VolumeCallback {
20431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
20441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be increased or decreased.
20451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
20461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param direction an integer indicating whether the volume is to be increased
20471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     (positive value) or decreased (negative value).
20481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     For bundled changes, the absolute value indicates the number of changes
20491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     in the same direction, e.g. +3 corresponds to three "volume up" changes.
20501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
20511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeUpdateRequest(RouteInfo info, int direction);
20521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
20531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be set to the given value
20541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
20551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume an integer indicating the new volume value that should be used, always
20561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     between 0 and the value set by {@link UserRouteInfo#setVolumeMax(int)}.
20571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
20581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeSetRequest(RouteInfo info, int volume);
20591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
20601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
20618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static class VolumeChangeReceiver extends BroadcastReceiver {
20628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
20638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onReceive(Context context, Intent intent) {
20648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
20658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
20668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        -1);
20678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (streamType != AudioManager.STREAM_MUSIC) {
20688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
20698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
20708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
20718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
20728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int oldVolume = intent.getIntExtra(
20738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
20748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (newVolume != oldVolume) {
20758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    systemVolumeChanged(newVolume);
20768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
20778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
20788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
2079705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
20808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
2081705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
2082705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        @Override
2083705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public void onReceive(Context context, Intent intent) {
2084705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
2085705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
2086705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
2087705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
2088705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
20898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
20909a1de308cea2d160778fd977825f10a07b49d738Adam Powell}
2091