MediaRouter.java revision f8e38e2dbbf3410179c9e18e05b7d4022720cfb6
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
19af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brownimport android.Manifest;
207b9c912f536925ac6ec43935d6e97506851b33d6Tor Norbyeimport android.annotation.DrawableRes;
219dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kangimport android.annotation.IntDef;
229d93a378c5158d14f80d46af70433234330ec568P.Y. Laligandimport android.annotation.NonNull;
23d86b8fea43ebb6e5c31691b44d8ceb0d8d3c9072Jeff Sharkeyimport android.annotation.SystemService;
24ba50b97cff80e73620a0e3d13cae169e095974a7Dianne Hackbornimport android.app.ActivityThread;
258e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.BroadcastReceiver;
269a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.content.Context;
278e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.Intent;
288e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.IntentFilter;
29af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brownimport android.content.pm.PackageManager;
30b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackbornimport android.content.res.Resources;
31ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powellimport android.graphics.drawable.Drawable;
32705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.hardware.display.DisplayManager;
33705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.hardware.display.WifiDisplay;
34705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.hardware.display.WifiDisplayStatus;
355d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErikimport android.media.session.MediaSession;
369a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.os.Handler;
37632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.IBinder;
38af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brownimport android.os.Process;
39632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.RemoteException;
40632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.ServiceManager;
4169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brownimport android.os.UserHandle;
42632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.text.TextUtils;
439a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.util.Log;
44705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powellimport android.view.Display;
459a1de308cea2d160778fd977825f10a07b49d738Adam Powell
469dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kangimport java.lang.annotation.Retention;
479dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kangimport java.lang.annotation.RetentionPolicy;
489a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.ArrayList;
499a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.HashMap;
50d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powellimport java.util.List;
51e6585b32ea586743258a5457e2184ffc087f2d2fKenny Rootimport java.util.Objects;
5239d5c6172503620ac3761148adac5fd7fa20d02dAdam Powellimport java.util.concurrent.CopyOnWriteArrayList;
539a1de308cea2d160778fd977825f10a07b49d738Adam Powell
549a1de308cea2d160778fd977825f10a07b49d738Adam Powell/**
559a1de308cea2d160778fd977825f10a07b49d738Adam Powell * MediaRouter allows applications to control the routing of media channels
569a1de308cea2d160778fd977825f10a07b49d738Adam Powell * and streams from the current device to external speakers and destination devices.
579a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
58b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
59b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
60b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.MEDIA_ROUTER_SERVICE}.
61b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn *
62b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>The media router API is not thread-safe; all interactions with it must be
63b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * done from the main thread of the process.</p>
649a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
65d86b8fea43ebb6e5c31691b44d8ceb0d8d3c9072Jeff Sharkey@SystemService(Context.MEDIA_ROUTER_SERVICE)
669a1de308cea2d160778fd977825f10a07b49d738Adam Powellpublic class MediaRouter {
679a1de308cea2d160778fd977825f10a07b49d738Adam Powell    private static final String TAG = "MediaRouter";
6869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
699a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    static class Static implements DisplayManager.DisplayListener {
71eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon        final String mPackageName;
72b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Resources mResources;
73632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioService mAudioService;
74705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final DisplayManager mDisplayService;
7569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        final IMediaRouterService mMediaRouterService;
76b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Handler mHandler;
7739d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
7839d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell                new CopyOnWriteArrayList<CallbackInfo>();
79b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
80b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
81b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
82b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
83b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final RouteCategory mSystemCategory;
84632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
85705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
86b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
87705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        RouteInfo mDefaultAudioVideo;
88b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mBluetoothA2dpRoute;
89b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
90b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mSelectedRoute;
919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
92af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown        final boolean mCanConfigureWifiDisplays;
9366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        boolean mActivelyScanningWifiDisplays;
94ce468a35b388ca46578934706b38dbae94941643Jeff Brown        String mPreviousActiveWifiDisplayAddress;
95705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
9669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        int mDiscoveryRequestRouteTypes;
9769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        boolean mDiscoverRequestActiveScan;
9869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
9969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        int mCurrentUserId = -1;
10069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        IMediaRouterClient mClient;
10169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        MediaRouterClientState mClientState;
10269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
103705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
10466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            @Override
105632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
106632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mHandler.post(new Runnable() {
107632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    @Override public void run() {
108705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        updateAudioRoutes(newRoutes);
109632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
110632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                });
111632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
112632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        };
113632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
114b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        Static(Context appContext) {
115eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon            mPackageName = appContext.getPackageName();
116eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon            mResources = appContext.getResources();
117b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mHandler = new Handler(appContext.getMainLooper());
1189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
119632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
120632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            mAudioService = IAudioService.Stub.asInterface(b);
1219a1de308cea2d160778fd977825f10a07b49d738Adam Powell
122705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
123705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
12469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            mMediaRouterService = IMediaRouterService.Stub.asInterface(
12569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
12669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
127dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            mSystemCategory = new RouteCategory(
128dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                    com.android.internal.R.string.default_audio_route_category_name,
129705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
130705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mSystemCategory.mIsSystem = true;
131af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown
132af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // Only the system can configure wifi displays.  The display manager
133af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // enforces this with a permission check.  Set a flag here so that we
134af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // know whether this process is actually allowed to scan and connect.
135af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            mCanConfigureWifiDisplays = appContext.checkPermission(
136af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    Manifest.permission.CONFIGURE_WIFI_DISPLAY,
137af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
138b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        }
139b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
140b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        // Called after sStatic is initialized
1418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        void startMonitoringRoutes(Context appContext) {
142705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo = new RouteInfo(mSystemCategory);
143705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
144705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
1455830b0b33618940d65197cec99d697b21908fec8Chong Zhang            mDefaultAudioVideo.updatePresentationDisplay();
146bd6118ffb453c7597c8679bc6bfefb4887d7662fSungsoo            if (((AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE))
147bd6118ffb453c7597c8679bc6bfefb4887d7662fSungsoo                    .isVolumeFixed()) {
148bd6118ffb453c7597c8679bc6bfefb4887d7662fSungsoo                mDefaultAudioVideo.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
149bd6118ffb453c7597c8679bc6bfefb4887d7662fSungsoo            }
150bd6118ffb453c7597c8679bc6bfefb4887d7662fSungsoo
1512ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            addRouteStatic(mDefaultAudioVideo);
152632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
1532ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // This will select the active wifi display route if there is one.
1542ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
1552ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell
1562ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
1572ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                    new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
1588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            appContext.registerReceiver(new VolumeChangeReceiver(),
1598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
1608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            mDisplayService.registerDisplayListener(this, mHandler);
16292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
163705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            AudioRoutesInfo newAudioRoutes = null;
164632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            try {
165705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
166632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            } catch (RemoteException e) {
167632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
168705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newAudioRoutes != null) {
1692ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // This will select the active BT route if there is one and the current
1702ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // selected route is the default system route, or if there is no selected
1712ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell                // route yet.
172705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateAudioRoutes(newAudioRoutes);
173632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
174705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
17569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Bind to the media router service.
17669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            rebindAsUser(UserHandle.myUserId());
17769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
1782ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // Select the default route if the above didn't sync us up
1792ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            // appropriately with relevant system state.
1802ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            if (mSelectedRoute == null) {
18169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                selectDefaultRouteStatic();
1822ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            }
183632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        }
184632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
185705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        void updateAudioRoutes(AudioRoutesInfo newRoutes) {
18676512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            boolean audioRoutesChanged = false;
1876156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock            if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
1886156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                mCurAudioRoutesInfo.mainType = newRoutes.mainType;
189632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                int name;
1906156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
1916156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                        || (newRoutes.mainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
192632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_headphones;
1936156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
194632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
1956156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
1964131a37366d59b5e61f55c4e48d2b22ee0c4cad4Adam Powell                    name = com.android.internal.R.string.default_media_route_name_hdmi;
197632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else {
198632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name;
199632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
200b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                mDefaultAudioVideo.mNameResId = name;
201b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                dispatchRouteChanged(mDefaultAudioVideo);
20276512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                audioRoutesChanged = true;
203632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
204bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell
2056156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock            if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
2066156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
2076156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                if (mCurAudioRoutesInfo.bluetoothName != null) {
208b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    if (mBluetoothA2dpRoute == null) {
209b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        // BT connected
210b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        final RouteInfo info = new RouteInfo(mSystemCategory);
2116156017c2217d0fbbbb03434986250ec6bbd69d8John Spurlock                        info.mName = mCurAudioRoutesInfo.bluetoothName;
212b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        info.mDescription = mResources.getText(
21356d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown                                com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
214632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
2159dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang                        info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH;
216b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        mBluetoothA2dpRoute = info;
217b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        addRouteStatic(mBluetoothA2dpRoute);
218632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    } else {
219b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName;
220b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                        dispatchRouteChanged(mBluetoothA2dpRoute);
221632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
222b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                } else if (mBluetoothA2dpRoute != null) {
223b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    // BT disconnected
224b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    removeRouteStatic(mBluetoothA2dpRoute);
225b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    mBluetoothA2dpRoute = null;
226bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell                }
22776512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                audioRoutesChanged = true;
228bcf21e913af7252fb1994e07b6cf179321ecd049Adam Powell            }
22976512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim
23076512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            if (audioRoutesChanged) {
231742e379863ff08a880cb6d9e5f274da7589f9a53Jaewan Kim                Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
232f8e38e2dbbf3410179c9e18e05b7d4022720cfb6Sungsoo Lim                if (mSelectedRoute == null || mSelectedRoute == mDefaultAudioVideo
233f8e38e2dbbf3410179c9e18e05b7d4022720cfb6Sungsoo Lim                        || mSelectedRoute == mBluetoothA2dpRoute) {
234f8e38e2dbbf3410179c9e18e05b7d4022720cfb6Sungsoo Lim                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false);
235f8e38e2dbbf3410179c9e18e05b7d4022720cfb6Sungsoo Lim                }
236742e379863ff08a880cb6d9e5f274da7589f9a53Jaewan Kim            }
237b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        }
23892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
23976512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim        RouteInfo getDefaultSystemAudioRoute() {
24076512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            boolean globalBluetoothA2doOn = false;
24176512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            try {
24276512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn();
24376512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            } catch (RemoteException ex) {
24476512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex);
24576512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            }
24676512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null)
24776512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
24876512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim        }
24976512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim
25076512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim        RouteInfo getCurrentSystemAudioRoute() {
25176512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim            return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null)
25276512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
25376512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim        }
25476512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim
255fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo        boolean isBluetoothA2dpOn() {
256fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            try {
257fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                return mAudioService.isBluetoothA2dpOn();
258fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            } catch (RemoteException e) {
259fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                Log.e(TAG, "Error querying Bluetooth A2DP state", e);
260fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                return false;
261fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            }
262fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo        }
263fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo
26469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void updateDiscoveryRequest() {
26569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // What are we looking for today?
26669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            int routeTypes = 0;
26769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            int passiveRouteTypes = 0;
26869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            boolean activeScan = false;
26969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            boolean activeScanWifiDisplay = false;
27069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            final int count = mCallbacks.size();
27169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            for (int i = 0; i < count; i++) {
27269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                CallbackInfo cbi = mCallbacks.get(i);
27369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
27469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {
27569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    // Discovery explicitly requested.
27669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    routeTypes |= cbi.type;
27769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {
27869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    // Discovery only passively requested.
27969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    passiveRouteTypes |= cbi.type;
28069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } else {
28169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    // Legacy case since applications don't specify the discovery flag.
28269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    // Unfortunately we just have to assume they always need discovery
28369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    // whenever they have a callback registered.
28469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    routeTypes |= cbi.type;
28569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
28669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
28769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    activeScan = true;
288af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
28969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        activeScanWifiDisplay = true;
29069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    }
29169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
29269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
29369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (routeTypes != 0 || activeScan) {
29469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                // If someone else requests discovery then enable the passive listeners.
29569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                // This is used by the MediaRouteButton and MediaRouteActionProvider since
29669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                // they don't receive lifecycle callbacks from the Activity.
29769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                routeTypes |= passiveRouteTypes;
29869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
29969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
30069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Update wifi display scanning.
301ce468a35b388ca46578934706b38dbae94941643Jeff Brown            // TODO: All of this should be managed by the media router service.
302ce468a35b388ca46578934706b38dbae94941643Jeff Brown            if (mCanConfigureWifiDisplays) {
303ce468a35b388ca46578934706b38dbae94941643Jeff Brown                if (mSelectedRoute != null
304ce468a35b388ca46578934706b38dbae94941643Jeff Brown                        && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {
305ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    // Don't scan while already connected to a remote display since
306ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    // it may interfere with the ongoing transmission.
307ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    activeScanWifiDisplay = false;
30866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown                }
309ce468a35b388ca46578934706b38dbae94941643Jeff Brown                if (activeScanWifiDisplay) {
310ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    if (!mActivelyScanningWifiDisplays) {
311ce468a35b388ca46578934706b38dbae94941643Jeff Brown                        mActivelyScanningWifiDisplays = true;
312ce468a35b388ca46578934706b38dbae94941643Jeff Brown                        mDisplayService.startWifiDisplayScan();
313ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    }
314ce468a35b388ca46578934706b38dbae94941643Jeff Brown                } else {
315ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    if (mActivelyScanningWifiDisplays) {
316ce468a35b388ca46578934706b38dbae94941643Jeff Brown                        mActivelyScanningWifiDisplays = false;
317ce468a35b388ca46578934706b38dbae94941643Jeff Brown                        mDisplayService.stopWifiDisplayScan();
318ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    }
31966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown                }
32066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            }
32166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
32269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Tell the media router service all about it.
32369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (routeTypes != mDiscoveryRequestRouteTypes
32469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    || activeScan != mDiscoverRequestActiveScan) {
32569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                mDiscoveryRequestRouteTypes = routeTypes;
32669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                mDiscoverRequestActiveScan = activeScan;
32769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                publishClientDiscoveryRequest();
32866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            }
32966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        }
33066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
33192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        @Override
33292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onDisplayAdded(int displayId) {
33392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            updatePresentationDisplays(displayId);
33492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
33592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
33692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        @Override
33792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onDisplayChanged(int displayId) {
33892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            updatePresentationDisplays(displayId);
33992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
34092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
34192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        @Override
34292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onDisplayRemoved(int displayId) {
34392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            updatePresentationDisplays(displayId);
34492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
34592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
34692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public Display[] getAllPresentationDisplays() {
34792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
34892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
34992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
35092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        private void updatePresentationDisplays(int changedDisplayId) {
35192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            final int count = mRoutes.size();
35292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            for (int i = 0; i < count; i++) {
3535830b0b33618940d65197cec99d697b21908fec8Chong Zhang                final RouteInfo route = mRoutes.get(i);
3545830b0b33618940d65197cec99d697b21908fec8Chong Zhang                if (route.updatePresentationDisplay() || (route.mPresentationDisplay != null
3555830b0b33618940d65197cec99d697b21908fec8Chong Zhang                        && route.mPresentationDisplay.getDisplayId() == changedDisplayId)) {
3565830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    dispatchRoutePresentationDisplayChanged(route);
35792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                }
35892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            }
35992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
36069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
36169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void setSelectedRoute(RouteInfo info, boolean explicit) {
36269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Must be non-reentrant.
36369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            mSelectedRoute = info;
36469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            publishClientSelectedRoute(explicit);
36569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
36669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
36769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void rebindAsUser(int userId) {
36869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (mCurrentUserId != userId || userId < 0 || mClient == null) {
36969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                if (mClient != null) {
37069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    try {
37169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        mMediaRouterService.unregisterClient(mClient);
37269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    } catch (RemoteException ex) {
37369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        Log.e(TAG, "Unable to unregister media router client.", ex);
37469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    }
37569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mClient = null;
37669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
37769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
37869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                mCurrentUserId = userId;
37969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
38069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                try {
38169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Client client = new Client();
382eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                    mMediaRouterService.registerClientAsUser(client, mPackageName, userId);
38369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mClient = client;
38469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } catch (RemoteException ex) {
38569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Log.e(TAG, "Unable to register media router client.", ex);
38669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
38769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
38869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                publishClientDiscoveryRequest();
38969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                publishClientSelectedRoute(false);
39069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                updateClientState();
39169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
39269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
39369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
39469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void publishClientDiscoveryRequest() {
39569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (mClient != null) {
39669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                try {
39769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mMediaRouterService.setDiscoveryRequest(mClient,
39869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan);
39969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } catch (RemoteException ex) {
40069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Log.e(TAG, "Unable to publish media router client discovery request.", ex);
40169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
40269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
40369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
40469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
40569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void publishClientSelectedRoute(boolean explicit) {
40669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (mClient != null) {
40769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                try {
40869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mMediaRouterService.setSelectedRoute(mClient,
40969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null,
41069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            explicit);
41169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } catch (RemoteException ex) {
41269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Log.e(TAG, "Unable to publish media router client selected route.", ex);
41369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
41469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
41569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
41669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
41769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void updateClientState() {
41869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Update the client state.
41969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            mClientState = null;
42069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (mClient != null) {
42169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                try {
42269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mClientState = mMediaRouterService.getState(mClient);
42369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } catch (RemoteException ex) {
42469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Log.e(TAG, "Unable to retrieve media router client state.", ex);
42569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
42669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
42769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
42869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mClientState != null ? mClientState.routes : null;
42969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
43069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Add or update routes.
43169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0;
43269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            for (int i = 0; i < globalRouteCount; i++) {
43369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                final MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(i);
43469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                RouteInfo route = findGlobalRoute(globalRoute.id);
43569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                if (route == null) {
43669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    route = makeGlobalRoute(globalRoute);
43769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    addRouteStatic(route);
43869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } else {
43969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    updateGlobalRoute(route, globalRoute);
44069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
44169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
44269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
44369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            // Remove defunct routes.
44469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            outer: for (int i = mRoutes.size(); i-- > 0; ) {
44569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                final RouteInfo route = mRoutes.get(i);
44669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                final String globalRouteId = route.mGlobalRouteId;
44769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                if (globalRouteId != null) {
44869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    for (int j = 0; j < globalRouteCount; j++) {
44969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(j);
45069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        if (globalRouteId.equals(globalRoute.id)) {
45169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            continue outer; // found
45269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        }
45369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    }
45469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    // not found
45569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    removeRouteStatic(route);
45669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
45769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
45869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
45969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
46069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void requestSetVolume(RouteInfo route, int volume) {
46169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mGlobalRouteId != null && mClient != null) {
46269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                try {
46369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mMediaRouterService.requestSetVolume(mClient,
46469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            route.mGlobalRouteId, volume);
46569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } catch (RemoteException ex) {
46669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Log.w(TAG, "Unable to request volume change.", ex);
46769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
46869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
46969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
47069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
47169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void requestUpdateVolume(RouteInfo route, int direction) {
47269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mGlobalRouteId != null && mClient != null) {
47369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                try {
47469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    mMediaRouterService.requestUpdateVolume(mClient,
47569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            route.mGlobalRouteId, direction);
47669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                } catch (RemoteException ex) {
47769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    Log.w(TAG, "Unable to request volume change.", ex);
47869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
47969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
48069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
48169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
48269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
483b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            RouteInfo route = new RouteInfo(mSystemCategory);
48469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mGlobalRouteId = globalRoute.id;
48569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mName = globalRoute.name;
48669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mDescription = globalRoute.description;
48769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mSupportedTypes = globalRoute.supportedTypes;
4889dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang            route.mDeviceType = globalRoute.deviceType;
48969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mEnabled = globalRoute.enabled;
49039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            route.setRealStatusCode(globalRoute.statusCode);
49169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mPlaybackType = globalRoute.playbackType;
49269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mPlaybackStream = globalRoute.playbackStream;
49369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mVolume = globalRoute.volume;
49469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mVolumeMax = globalRoute.volumeMax;
49569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            route.mVolumeHandling = globalRoute.volumeHandling;
4965830b0b33618940d65197cec99d697b21908fec8Chong Zhang            route.mPresentationDisplayId = globalRoute.presentationDisplayId;
4975830b0b33618940d65197cec99d697b21908fec8Chong Zhang            route.updatePresentationDisplay();
49869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            return route;
49969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
50069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
50169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        void updateGlobalRoute(RouteInfo route, MediaRouterClientState.RouteInfo globalRoute) {
50269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            boolean changed = false;
50369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            boolean volumeChanged = false;
50469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            boolean presentationDisplayChanged = false;
50569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
506e6585b32ea586743258a5457e2184ffc087f2d2fKenny Root            if (!Objects.equals(route.mName, globalRoute.name)) {
50769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mName = globalRoute.name;
50869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
50969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
510e6585b32ea586743258a5457e2184ffc087f2d2fKenny Root            if (!Objects.equals(route.mDescription, globalRoute.description)) {
51169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mDescription = globalRoute.description;
51269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
51369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
514af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            final int oldSupportedTypes = route.mSupportedTypes;
515af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            if (oldSupportedTypes != globalRoute.supportedTypes) {
51669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mSupportedTypes = globalRoute.supportedTypes;
51769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
51869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
51969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mEnabled != globalRoute.enabled) {
52069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mEnabled = globalRoute.enabled;
52169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
52269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
52339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            if (route.mRealStatusCode != globalRoute.statusCode) {
52439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                route.setRealStatusCode(globalRoute.statusCode);
52569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
52669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
52769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mPlaybackType != globalRoute.playbackType) {
52869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mPlaybackType = globalRoute.playbackType;
52969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
53069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
53169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mPlaybackStream != globalRoute.playbackStream) {
53269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mPlaybackStream = globalRoute.playbackStream;
53369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
53469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
53569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mVolume != globalRoute.volume) {
53669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mVolume = globalRoute.volume;
53769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
53869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                volumeChanged = true;
53969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
54069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mVolumeMax != globalRoute.volumeMax) {
54169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mVolumeMax = globalRoute.volumeMax;
54269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
54369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                volumeChanged = true;
54469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
54569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (route.mVolumeHandling != globalRoute.volumeHandling) {
54669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                route.mVolumeHandling = globalRoute.volumeHandling;
54769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
54869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                volumeChanged = true;
54969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
5505830b0b33618940d65197cec99d697b21908fec8Chong Zhang            if (route.mPresentationDisplayId != globalRoute.presentationDisplayId) {
5515830b0b33618940d65197cec99d697b21908fec8Chong Zhang                route.mPresentationDisplayId = globalRoute.presentationDisplayId;
5525830b0b33618940d65197cec99d697b21908fec8Chong Zhang                route.updatePresentationDisplay();
55369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                changed = true;
55469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                presentationDisplayChanged = true;
55569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
55669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
55769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (changed) {
558af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                dispatchRouteChanged(route, oldSupportedTypes);
55969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
56069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (volumeChanged) {
56169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                dispatchRouteVolumeChanged(route);
56269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
56369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            if (presentationDisplayChanged) {
56469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                dispatchRoutePresentationDisplayChanged(route);
56569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
56669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
56769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
56869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        RouteInfo findGlobalRoute(String globalRouteId) {
56969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            final int count = mRoutes.size();
57069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            for (int i = 0; i < count; i++) {
57169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                final RouteInfo route = mRoutes.get(i);
57269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                if (globalRouteId.equals(route.mGlobalRouteId)) {
57369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    return route;
57469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                }
57569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
57669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            return null;
57769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
57869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
579b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo        boolean isPlaybackActive() {
580b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            if (mClient != null) {
581b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                try {
582b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    return mMediaRouterService.isPlaybackActive(mClient);
583b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                } catch (RemoteException ex) {
584b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    Log.e(TAG, "Unable to retrieve playback active state.", ex);
585b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                }
586b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            }
587b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            return false;
588b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo        }
589b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo
59069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        final class Client extends IMediaRouterClient.Stub {
59169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            @Override
59269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            public void onStateChanged() {
59369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                mHandler.post(new Runnable() {
59469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    @Override
59569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    public void run() {
59669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        if (Client.this == mClient) {
59769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                            updateClientState();
59869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        }
59969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    }
60069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                });
60169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            }
602b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo
603b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            @Override
604b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            public void onRestoreRoute() {
60576512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                // Skip restoring route if the selected route is not a system audio route, or
60676512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                // MediaRouter is initializing.
607b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
60876512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                        || mSelectedRoute == null) {
609b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                    return;
610b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                }
61176512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                mSelectedRoute.select();
612b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            }
61369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
614b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
6159a1de308cea2d160778fd977825f10a07b49d738Adam Powell
616b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static Static sStatic;
6179a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6189a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
6199a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for live audio.
6209a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
6219a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A device that supports live audio routing will allow the media audio stream
6229a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to be routed to supported destinations. This can include internal speakers or
6239a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * audio jacks on the device itself, A2DP devices, and more.</p>
6249a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
6259a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Once initiated this routing is transparent to the application. All audio
6269a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * played on the media stream will be routed to the selected destination.</p>
6279a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
62869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public static final int ROUTE_TYPE_LIVE_AUDIO = 1 << 0;
6299a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6309a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
631705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Route type flag for live video.
632705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     *
633705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * <p>A device that supports live video routing will allow a mirrored version
634705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * of the device's primary display or a customized
635705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
636705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     *
637705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * <p>Once initiated, display mirroring is transparent to the application.
638705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * While remote routing is active the application may use a
639705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * {@link android.app.Presentation Presentation} to replace the mirrored view
640705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * on the external display with different content.</p>
64192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown     *
64292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown     * @see RouteInfo#getPresentationDisplay()
64392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown     * @see android.app.Presentation
644705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     */
64569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public static final int ROUTE_TYPE_LIVE_VIDEO = 1 << 1;
64669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
64769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    /**
64869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * Temporary interop constant to identify remote displays.
64969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * @hide To be removed when media router API is updated.
65069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     */
65169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public static final int ROUTE_TYPE_REMOTE_DISPLAY = 1 << 2;
652705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
653705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    /**
6549a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for application-specific usage.
6559a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
6569a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Unlike other media route types, user routes are managed by the application.
6579a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * The MediaRouter will manage and dispatch events for user routes, but the application
6589a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * is expected to interpret the meaning of these events and perform the requested
6599a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * routing tasks.</p>
6609a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
66169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public static final int ROUTE_TYPE_USER = 1 << 23;
66269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
66369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    static final int ROUTE_TYPE_ANY = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
66469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            | ROUTE_TYPE_REMOTE_DISPLAY | ROUTE_TYPE_USER;
6659a1de308cea2d160778fd977825f10a07b49d738Adam Powell
66666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    /**
66766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * Flag for {@link #addCallback}: Actively scan for routes while this callback
66866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * is registered.
66966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * <p>
67066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * When this flag is specified, the media router will actively scan for new
67166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * routes.  Certain routes, such as wifi display routes, may not be discoverable
67266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * except when actively scanning.  This flag is typically used when the route picker
67366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * dialog has been opened by the user to ensure that the route information is
67466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * up to date.
67566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * </p><p>
67666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * Active scanning may consume a significant amount of power and may have intrusive
67766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * effects on wireless connectivity.  Therefore it is important that active scanning
67866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * only be requested when it is actually needed to satisfy a user request to
67966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * discover and select a new route.
68066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * </p>
68166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     */
68214507e257af5d71577574e25cbd690c4b54c9272Jeff Brown    public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
68366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
68466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    /**
68566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * Flag for {@link #addCallback}: Do not filter route events.
68666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * <p>
68766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * When this flag is specified, the callback will be invoked for event that affect any
68869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * route even if they do not match the callback's filter.
68966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * </p>
69066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     */
69166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
69266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
69369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    /**
69469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * Explicitly requests discovery.
69569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     *
69669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * @hide Future API ported from support library.  Revisit this later.
69769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     */
69869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
69969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
70069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    /**
70169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * Requests that discovery be performed but only if there is some other active
70269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * callback already registered.
70369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     *
70469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * @hide Compatibility workaround for the fact that applications do not currently
70569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * request discovery explicitly (except when using the support library API).
70669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     */
70769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3;
70869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
7090abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    /**
7100abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * Flag for {@link #isRouteAvailable}: Ignore the default route.
7110abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * <p>
7120abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * This flag is used to determine whether a matching non-default route is available.
7130abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * This constraint may be used to decide whether to offer the route chooser dialog
7140abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * to the user.  There is no point offering the chooser if there are no
7150abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * non-default choices.
7160abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * </p>
7170abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     *
7180abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * @hide Future API ported from support library.  Revisit this later.
7190abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     */
7200abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
7210abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
7229a1de308cea2d160778fd977825f10a07b49d738Adam Powell    // Maps application contexts
7239a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
7249a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7259a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static String typesToString(int types) {
7269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final StringBuilder result = new StringBuilder();
7279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
7289a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_LIVE_AUDIO ");
7299a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7302bb7c122ef6ce8539dfbaeb3292adcd942185f82Bryan Mawhinney        if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
7312bb7c122ef6ce8539dfbaeb3292adcd942185f82Bryan Mawhinney            result.append("ROUTE_TYPE_LIVE_VIDEO ");
7322bb7c122ef6ce8539dfbaeb3292adcd942185f82Bryan Mawhinney        }
73369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        if ((types & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
73469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            result.append("ROUTE_TYPE_REMOTE_DISPLAY ");
73569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
7369a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_USER) != 0) {
7379a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_USER ");
7389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7399a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return result.toString();
7409a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
7419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
742b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    /** @hide */
743b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public MediaRouter(Context context) {
744b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        synchronized (Static.class) {
745b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic == null) {
7468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final Context appContext = context.getApplicationContext();
7478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic = new Static(appContext);
7488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic.startMonitoringRoutes(appContext);
749b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            }
7509a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7519a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
7529a1de308cea2d160778fd977825f10a07b49d738Adam Powell
753690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
7543afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * Gets the default route for playing media content on the system.
7553afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * <p>
7563afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * The system always provides a default route.
7573afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * </p>
7583afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     *
7593afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * @return The default route, which is guaranteed to never be null.
760690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
7613afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown    public RouteInfo getDefaultRoute() {
762705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return sStatic.mDefaultAudioVideo;
763690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
764690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
765690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
76659579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim     * Returns a Bluetooth route if available, otherwise the default route.
76759579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim     * @hide
76859579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim     */
76959579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim    public RouteInfo getFallbackRoute() {
77059579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim        return (sStatic.mBluetoothA2dpRoute != null)
77159579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo;
77259579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim    }
77359579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim
77459579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim    /**
7754599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     * @hide for use by framework routing UI
7764599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     */
7773afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown    public RouteCategory getSystemCategory() {
7784599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        return sStatic.mSystemCategory;
7794599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    }
7804599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
7810abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    /** @hide */
7820abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    public RouteInfo getSelectedRoute() {
7830abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        return getSelectedRoute(ROUTE_TYPE_ANY);
7840abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    }
7850abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
7864599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    /**
7871cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell     * Return the currently selected route for any of the given types
788690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
789690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @param type route types
790690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @return the selected route
791690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
792690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSelectedRoute(int type) {
7931cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell        if (sStatic.mSelectedRoute != null &&
7941cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell                (sStatic.mSelectedRoute.mSupportedTypes & type) != 0) {
7951cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell            // If the selected route supports any of the types supplied, it's still considered
7961cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell            // 'selected' for that type.
7971cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell            return sStatic.mSelectedRoute;
7981cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell        } else if (type == ROUTE_TYPE_USER) {
7991cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell            // The caller specifically asked for a user route and the currently selected route
8001cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell            // doesn't qualify.
8011cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell            return null;
8021cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell        }
8031cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell        // If the above didn't match and we're not specifically asking for a user route,
8041cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell        // consider the default selected.
8051cf2ca83584a4cf0aa3ded787bd191b9a60e3521Adam Powell        return sStatic.mDefaultAudioVideo;
806690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
807690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
8089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
8090abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * Returns true if there is a route that matches the specified types.
8100abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * <p>
8110abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * This method returns true if there are any available routes that match the types
8120abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * regardless of whether they are enabled or disabled.  If the
8130abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
8140abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * the method will only consider non-default routes.
8150abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * </p>
8160abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     *
8170abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * @param types The types to match.
8180abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * @param flags Flags to control the determination of whether a route may be available.
8190abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
8200abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * @return True if a matching route may be available.
8210abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     *
8220abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     * @hide Future API ported from support library.  Revisit this later.
8230abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown     */
8240abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    public boolean isRouteAvailable(int types, int flags) {
8250abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        final int count = sStatic.mRoutes.size();
8260abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        for (int i = 0; i < count; i++) {
8270abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            RouteInfo route = sStatic.mRoutes.get(i);
8280abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            if (route.matchesTypes(types)) {
8290abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0
8300abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown                        || route != sStatic.mDefaultAudioVideo) {
8310abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown                    return true;
8320abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown                }
8330abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            }
8340abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        }
8350abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
8360abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        // It doesn't look like we can find a matching route right now.
8370abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        return false;
8380abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    }
8390abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
8400abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown    /**
8419a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add a callback to listen to events about specific kinds of media routes.
8429a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * If the specified callback is already registered, its registration will be updated for any
8439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * additional route types specified.
84466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * <p>
84566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * This is a convenience method that has the same effect as calling
84666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * {@link #addCallback(int, Callback, int)} without flags.
84766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * </p>
8489a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
8499a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param types Types of routes this callback is interested in
8509a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to add
8519a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
8529a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addCallback(int types, Callback cb) {
85366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        addCallback(types, cb, 0);
85466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    }
85566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
85666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    /**
85766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * Add a callback to listen to events about specific kinds of media routes.
85866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * If the specified callback is already registered, its registration will be updated for any
85966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * additional route types specified.
86066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * <p>
86166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * By default, the callback will only be invoked for events that affect routes
86266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * that match the specified selector.  The filtering may be disabled by specifying
86366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag.
86466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * </p>
86566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     *
86666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * @param types Types of routes this callback is interested in
86766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * @param cb Callback to add
86866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * @param flags Flags to control the behavior of the callback.
86914507e257af5d71577574e25cbd690c4b54c9272Jeff Brown     * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
87066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
87166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     */
87266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    public void addCallback(int types, Callback cb, int flags) {
87366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        CallbackInfo info;
87466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        int index = findCallbackInfo(cb);
87566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        if (index >= 0) {
87666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            info = sStatic.mCallbacks.get(index);
87766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            info.type |= types;
87866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            info.flags |= flags;
87966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        } else {
88066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            info = new CallbackInfo(cb, types, flags, this);
88166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            sStatic.mCallbacks.add(info);
88266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        }
88369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        sStatic.updateDiscoveryRequest();
8849a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
8859a1de308cea2d160778fd977825f10a07b49d738Adam Powell
8869a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
8879a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove the specified callback. It will no longer receive events about media routing.
8889a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
8899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to remove
8909a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
8919a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeCallback(Callback cb) {
89266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        int index = findCallbackInfo(cb);
89366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        if (index >= 0) {
89469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            sStatic.mCallbacks.remove(index);
89569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            sStatic.updateDiscoveryRequest();
89666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        } else {
89766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
89866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        }
89966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    }
90066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
90166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown    private int findCallbackInfo(Callback cb) {
902b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
9039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
90466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            final CallbackInfo info = sStatic.mCallbacks.get(i);
90566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (info.cb == cb) {
90666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown                return i;
9079a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
9089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
90966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        return -1;
9109a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
9119a1de308cea2d160778fd977825f10a07b49d738Adam Powell
912d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    /**
913d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * Select the specified route to use for output of the given media types.
9143afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * <p class="note">
9153afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * As API version 18, this function may be used to select any route.
9163afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * In prior versions, this function could only be used to select user
9173afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * routes and would ignore any attempt to select a system route.
9183afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * </p>
919d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *
920d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param types type flags indicating which types this route should be used for.
921d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *              The route must support at least a subset.
922d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param route Route to select
9239d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand     * @throws IllegalArgumentException if the given route is {@code null}
924d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     */
9259d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand    public void selectRoute(int types, @NonNull RouteInfo route) {
9269d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand        if (route == null) {
9279d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand            throw new IllegalArgumentException("Route cannot be null.");
9289d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand        }
92969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        selectRouteStatic(types, route, true);
9300d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
93169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
9320d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
9330d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @hide internal use
9340d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
93569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public void selectRouteInt(int types, RouteInfo route, boolean explicit) {
93669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        selectRouteStatic(types, route, explicit);
937b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
938b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
9399d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand    static void selectRouteStatic(int types, @NonNull RouteInfo route, boolean explicit) {
94018687b7a275b3782e63d52cdbe8ee9df191046e7Jae Seo        Log.v(TAG, "Selecting route: " + route);
9419d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand        assert(route != null);
942705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final RouteInfo oldRoute = sStatic.mSelectedRoute;
943b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo        boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
944b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                || oldRoute == sStatic.mBluetoothA2dpRoute);
945b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo        if (oldRoute == route
94676512a3d80dff2d32b68f3b3b6a14d4ed81ae90aSungsoo Lim                && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) {
947b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo            return;
948b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo        }
9490abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        if (!route.matchesTypes(types)) {
9500d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
9510d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(route.getSupportedTypes()) + " into route types " +
9520d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(types));
9534ee1f55ce0f4909a7430ab44563a81852f335071Adam Powell            return;
9540d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9559a1de308cea2d160778fd977825f10a07b49d738Adam Powell
956fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
957b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo        if (sStatic.isPlaybackActive() && btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0
958b3658569e9a87afe1b3168837cc282235fd4d1f8Sungsoo                && (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
959fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            try {
960fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
96159579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                // TODO: Remove the following logging when no longer needed.
96259579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                if (route != btRoute) {
96359579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                    StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
96459579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                    StringBuffer sb = new StringBuffer();
96559579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                    // callStack[3] is the caller of this method.
96659579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                    for (int i = 3; i < callStack.length; i++) {
96759579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                        StackTraceElement caller = callStack[i];
96859579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                        sb.append(caller.getClassName() + "." + caller.getMethodName()
96959579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                                + ":" + caller.getLineNumber()).append("  ");
97059579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                    }
97159579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                    Log.w(TAG, "Default route is selected while a BT route is available: pkgName="
97259579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                            + sStatic.mPackageName + ", callers=" + sb.toString());
97359579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim                }
974fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            } catch (RemoteException e) {
975fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                Log.e(TAG, "Error changing Bluetooth A2DP state", e);
976fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            }
977fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo        }
978fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo
979705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final WifiDisplay activeDisplay =
980705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
981705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
9829d93a378c5158d14f80d46af70433234330ec568P.Y. Laligand        final boolean newRouteHasAddress = route.mDeviceAddress != null;
983705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
984705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
985af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                if (sStatic.mCanConfigureWifiDisplays) {
986af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
987af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                } else {
988af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    Log.e(TAG, "Cannot connect to wifi displays because this process "
989af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                            + "is not allowed to do so.");
990af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                }
991705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            } else if (activeDisplay != null && !newRouteHasAddress) {
992705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                sStatic.mDisplayService.disconnectWifiDisplay();
993705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
994705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
995705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
99669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        sStatic.setSelectedRoute(route, explicit);
99769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
998705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (oldRoute != null) {
999705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
100039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            if (oldRoute.resolveStatusCode()) {
100139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                dispatchRouteChanged(oldRoute);
100239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            }
10039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
10049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (route != null) {
100539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            if (route.resolveStatusCode()) {
100639ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                dispatchRouteChanged(route);
100739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            }
10089a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteSelected(types & route.getSupportedTypes(), route);
10099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1010ce468a35b388ca46578934706b38dbae94941643Jeff Brown
1011ce468a35b388ca46578934706b38dbae94941643Jeff Brown        // The behavior of active scans may depend on the currently selected route.
1012ce468a35b388ca46578934706b38dbae94941643Jeff Brown        sStatic.updateDiscoveryRequest();
10139a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
10149a1de308cea2d160778fd977825f10a07b49d738Adam Powell
101569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    static void selectDefaultRouteStatic() {
101669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        // TODO: Be smarter about the route types here; this selects for all valid.
101769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
1018fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                && sStatic.mBluetoothA2dpRoute != null && sStatic.isBluetoothA2dpOn()) {
101969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
102069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        } else {
102169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
102269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        }
102369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    }
102469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
10259a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
1026705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Compare the device address of a display and a route.
1027705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     * Nulls/no device address will match another null/no address.
1028705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell     */
1029705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
1030705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
1031705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display == null && !routeHasAddress) {
1032705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return true;
1033705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1034705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1035705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (display != null && routeHasAddress) {
1036705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return display.getDeviceAddress().equals(info.mDeviceAddress);
1037705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1038705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return false;
1039705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
1040705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1041705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    /**
10429a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add an app-specified route for media to the MediaRouter.
10439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
10449a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
10459a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to add
10463afc18af2cea898753b10e8575dcf20c11356bcaJeff Brown     * @see #createUserRoute(RouteCategory)
10479a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
10489a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
10499a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addUserRoute(UserRouteInfo info) {
10502ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        addRouteStatic(info);
10519a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
10529a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1053d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
1054d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide Framework use only
1055d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
1056d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void addRouteInt(RouteInfo info) {
10572ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        addRouteStatic(info);
1058d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
1059d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
10602ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell    static void addRouteStatic(RouteInfo info) {
106118687b7a275b3782e63d52cdbe8ee9df191046e7Jae Seo        Log.v(TAG, "Adding route: " + info);
10629a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory cat = info.getCategory();
1063b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (!sStatic.mCategories.contains(cat)) {
1064b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mCategories.add(cat);
10659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1066d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        if (cat.isGroupable() && !(info instanceof RouteGroup)) {
10679a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // Enforce that any added route in a groupable category must be in a group.
10689a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteGroup group = new RouteGroup(info.getCategory());
1069dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell            group.mSupportedTypes = info.mSupportedTypes;
1070b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(group);
1071d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(group);
1072b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            group.addRoute(info);
1073d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
10749a1de308cea2d160778fd977825f10a07b49d738Adam Powell            info = group;
1075d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        } else {
1076b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(info);
1077d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(info);
10789a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
10799a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
10809a1de308cea2d160778fd977825f10a07b49d738Adam Powell
10819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
10829a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove an app-specified route for media from the MediaRouter.
10839a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
10849a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to remove
10859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
10869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
10879a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeUserRoute(UserRouteInfo info) {
108869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        removeRouteStatic(info);
10899a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
10909a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1091690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
1092690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Remove all app-specified routes from the MediaRouter.
1093690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
1094690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @see #removeUserRoute(UserRouteInfo)
1095690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
1096690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public void clearUserRoutes() {
1097b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        for (int i = 0; i < sStatic.mRoutes.size(); i++) {
1098b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.get(i);
1099d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // TODO Right now, RouteGroups only ever contain user routes.
1100d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // The code below will need to change if this assumption does.
1101d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
110269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                removeRouteStatic(info);
1103690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                i--;
1104690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
1105690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
1106690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
1107690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
1108d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
1109d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide internal use only
1110d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
1111d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void removeRouteInt(RouteInfo info) {
111269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        removeRouteStatic(info);
1113d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
1114d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
111569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    static void removeRouteStatic(RouteInfo info) {
111618687b7a275b3782e63d52cdbe8ee9df191046e7Jae Seo        Log.v(TAG, "Removing route: " + info);
1117b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mRoutes.remove(info)) {
11189a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteCategory removingCat = info.getCategory();
1119b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
11209a1de308cea2d160778fd977825f10a07b49d738Adam Powell            boolean found = false;
11219a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
1122b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
11239a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (removingCat == cat) {
11249a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    found = true;
11259a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    break;
11269a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
11279a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11280abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            if (info.isSelected()) {
1129d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
113069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                selectDefaultRouteStatic();
1131d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
1132690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            if (!found) {
1133b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
1134690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
1135690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            dispatchRouteRemoved(info);
1136690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
1137690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
1138690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
11399a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11409a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteCategory categories} currently
11419a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * represented by routes known to this MediaRouter.
11429a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11439a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of unique categories represented by this MediaRouter's known routes
11449a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
11459a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getCategoryCount() {
1146b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.size();
11479a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11489a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11499a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11509a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the {@link MediaRouter.RouteCategory category} at the given index.
11519a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Valid indices are in the range [0-getCategoryCount).
11529a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11539a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index which category to return
11549a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the category at index
11559a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
11569a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory getCategoryAt(int index) {
1157b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.get(index);
11589a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11599a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11609a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11619a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteInfo routes} currently known
11629a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to this MediaRouter.
11639a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11649a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of routes tracked by this router
11659a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
11669a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getRouteCount() {
1167b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
11689a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11699a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11709a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11719a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the route at the specified index.
11729a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11739a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index index of the route to return
11749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the route at index
11759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
11769a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteInfo getRouteAt(int index) {
1177b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
1178b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
1179b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
1180b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static int getRouteCountStatic() {
1181b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
1182b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
1183b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
1184b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static RouteInfo getRouteAtStatic(int index) {
1185b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
11869a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11879a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11889a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11899a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new user route that may be modified and registered for use by the application.
11909a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11919a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param category The category the new route will belong to
11929a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return A new UserRouteInfo for use by the application
11939a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
11949a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
11959a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
119669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * @see #createRouteCategory(CharSequence, boolean)
11979a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
11989a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public UserRouteInfo createUserRoute(RouteCategory category) {
11999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new UserRouteInfo(category);
12009a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12019a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12029a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
12039a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new route category. Each route must belong to a category.
12049a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
12059a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param name Name of the new category
12069a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
12079a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the new RouteCategory
12089a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
12099a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
12109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
12119a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12125d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
12130d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
12140d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Create a new route category. Each route must belong to a category.
12150d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     *
12160d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param nameResId Resource ID of the name of the new category
12170d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
12180d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @return the new RouteCategory
12190d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
12200d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
12210d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
12220d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
12239a1de308cea2d160778fd977825f10a07b49d738Adam Powell
122469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    /**
122569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * Rebinds the media router to handle routes that belong to the specified user.
122669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * Requires the interact across users permission to access the routes of another user.
122769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * <p>
122869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * This method is a complete hack to work around the singleton nature of the
122969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * media router when running inside of singleton processes like QuickSettings.
123069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * This mechanism should be burned to the ground when MediaRouter is redesigned.
123169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * Ideally the current user would be pulled from the Context but we need to break
123269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * down MediaRouter.Static before we can get there.
123369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * </p>
123469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     *
123569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     * @hide
123669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown     */
123769b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    public void rebindAsUser(int userId) {
123869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        sStatic.rebindAsUser(userId);
123969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown    }
124069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
1241b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void updateRoute(final RouteInfo info) {
12429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        dispatchRouteChanged(info);
12439a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12449a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1245b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteSelected(int type, RouteInfo info) {
124639d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
124766f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(info)) {
1248b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteSelected(cbi.router, type, info);
12499a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
12509a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12519a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12529a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1253b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUnselected(int type, RouteInfo info) {
125439d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
125566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(info)) {
1256b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUnselected(cbi.router, type, info);
12579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
12589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12609a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1261b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteChanged(RouteInfo info) {
1262af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown        dispatchRouteChanged(info, info.mSupportedTypes);
1263af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown    }
1264af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown
1265af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown    static void dispatchRouteChanged(RouteInfo info, int oldSupportedTypes) {
12664bad663518908c20ed4f93a27f46ffb09448497fSungsoo Lim        if (DEBUG) {
12674bad663518908c20ed4f93a27f46ffb09448497fSungsoo Lim            Log.d(TAG, "Dispatching route change: " + info);
12684bad663518908c20ed4f93a27f46ffb09448497fSungsoo Lim        }
1269af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown        final int newSupportedTypes = info.mSupportedTypes;
127039d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
1271af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // Reconstruct some of the history for callbacks that may not have observed
1272af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // all of the events needed to correctly interpret the current state.
1273af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // FIXME: This is a strong signal that we should deprecate route type filtering
1274af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // completely in the future because it can lead to inconsistencies in
1275af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // applications.
1276af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            final boolean oldVisibility = cbi.filterRouteEvent(oldSupportedTypes);
1277af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            final boolean newVisibility = cbi.filterRouteEvent(newSupportedTypes);
1278af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            if (!oldVisibility && newVisibility) {
1279af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                cbi.cb.onRouteAdded(cbi.router, info);
1280af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                if (info.isSelected()) {
1281af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    cbi.cb.onRouteSelected(cbi.router, newSupportedTypes, info);
1282af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                }
1283af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            }
1284af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            if (oldVisibility || newVisibility) {
1285b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteChanged(cbi.router, info);
12869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
1287af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            if (oldVisibility && !newVisibility) {
1288af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                if (info.isSelected()) {
1289af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    cbi.cb.onRouteUnselected(cbi.router, oldSupportedTypes, info);
1290af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                }
1291af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                cbi.cb.onRouteRemoved(cbi.router, info);
1292af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            }
12939a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12949a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
12959a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1296b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteAdded(RouteInfo info) {
129739d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
129866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(info)) {
1299b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteAdded(cbi.router, info);
13009a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
13019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13029a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
13039a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1304b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteRemoved(RouteInfo info) {
130539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
130666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(info)) {
1307b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteRemoved(cbi.router, info);
13089a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
13099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13109a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
13119a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1312b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
131339d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
131466f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(group)) {
1315b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteGrouped(cbi.router, info, group, index);
1316d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1317d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1318d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    }
1319d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1320b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
132139d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
132266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(group)) {
1323b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUngrouped(cbi.router, info, group);
13249a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
13259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13269a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
13279a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void dispatchRouteVolumeChanged(RouteInfo info) {
13298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
133066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(info)) {
13318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                cbi.cb.onRouteVolumeChanged(cbi.router, info);
13328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
13338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
13348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
13358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
133692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    static void dispatchRoutePresentationDisplayChanged(RouteInfo info) {
133792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        for (CallbackInfo cbi : sStatic.mCallbacks) {
133866f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            if (cbi.filterRouteEvent(info)) {
133992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info);
134092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            }
134192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
134292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown    }
134392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
13448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void systemVolumeChanged(int newValue) {
13458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        final RouteInfo selectedRoute = sStatic.mSelectedRoute;
13468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == null) return;
13478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
13488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
1349705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                selectedRoute == sStatic.mDefaultAudioVideo) {
13508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(selectedRoute);
13518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else if (sStatic.mBluetoothA2dpRoute != null) {
1352fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            try {
1353fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
1354fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
1355fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            } catch (RemoteException e) {
1356fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
1357fa6f70e225a7370c1ecb21ee7076c9086ecb9e9aSungsoo            }
13588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else {
1359705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
1360705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1361705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
1362705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
136375af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown    static void updateWifiDisplayStatus(WifiDisplayStatus status) {
136475af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        WifiDisplay[] displays;
1365615e413a90ff5898d8c458ebc9649ca95fa9fd98Adam Powell        WifiDisplay activeDisplay;
136675af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
136775af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            displays = status.getDisplays();
136875af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            activeDisplay = status.getActiveDisplay();
1369af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown
1370af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // Only the system is able to connect to wifi display routes.
1371af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // The display manager will enforce this with a permission check but it
1372af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // still publishes information about all available displays.
1373af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            // Filter the list down to just the active display.
1374af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            if (!sStatic.mCanConfigureWifiDisplays) {
1375af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                if (activeDisplay != null) {
1376af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    displays = new WifiDisplay[] { activeDisplay };
1377af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                } else {
1378af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    displays = WifiDisplay.EMPTY_ARRAY;
1379af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                }
1380af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            }
1381615e413a90ff5898d8c458ebc9649ca95fa9fd98Adam Powell        } else {
138275af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            displays = WifiDisplay.EMPTY_ARRAY;
1383615e413a90ff5898d8c458ebc9649ca95fa9fd98Adam Powell            activeDisplay = null;
1384615e413a90ff5898d8c458ebc9649ca95fa9fd98Adam Powell        }
1385ce468a35b388ca46578934706b38dbae94941643Jeff Brown        String activeDisplayAddress = activeDisplay != null ?
1386ce468a35b388ca46578934706b38dbae94941643Jeff Brown                activeDisplay.getDeviceAddress() : null;
1387705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
138875af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        // Add or update routes.
138975af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        for (int i = 0; i < displays.length; i++) {
139075af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            final WifiDisplay d = displays[i];
139175af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            if (shouldShowWifiDisplay(d, activeDisplay)) {
1392ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                RouteInfo route = findWifiDisplayRoute(d);
1393ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                if (route == null) {
139475af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown                    route = makeWifiDisplayRoute(d, status);
1395ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                    addRouteStatic(route);
1396ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                } else {
1397ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    String address = d.getDeviceAddress();
1398ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    boolean disconnected = !address.equals(activeDisplayAddress)
1399ce468a35b388ca46578934706b38dbae94941643Jeff Brown                            && address.equals(sStatic.mPreviousActiveWifiDisplayAddress);
1400ce468a35b388ca46578934706b38dbae94941643Jeff Brown                    updateWifiDisplayRoute(route, d, status, disconnected);
1401ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                }
1402ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                if (d.equals(activeDisplay)) {
140369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    selectRouteStatic(route.getSupportedTypes(), route, false);
1404ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                }
14052ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell            }
1406705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
140775af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown
140875af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        // Remove stale routes.
140975af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        for (int i = sStatic.mRoutes.size(); i-- > 0; ) {
141075af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            RouteInfo route = sStatic.mRoutes.get(i);
141175af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            if (route.mDeviceAddress != null) {
141275af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown                WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress);
141375af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown                if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) {
141475af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown                    removeRouteStatic(route);
1415ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                }
1416705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
1417705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1418ce468a35b388ca46578934706b38dbae94941643Jeff Brown
1419ce468a35b388ca46578934706b38dbae94941643Jeff Brown        // Remember the current active wifi display address so that we can infer disconnections.
1420ce468a35b388ca46578934706b38dbae94941643Jeff Brown        // TODO: This hack will go away once all of this is moved into the media router service.
1421ce468a35b388ca46578934706b38dbae94941643Jeff Brown        sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;
142275af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown    }
1423705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
142475af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown    private static boolean shouldShowWifiDisplay(WifiDisplay d, WifiDisplay activeDisplay) {
142575af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        return d.isRemembered() || d.equals(activeDisplay);
1426705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
1427705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1428ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) {
142969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        int newStatus;
1430ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) {
1431ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang            newStatus = RouteInfo.STATUS_SCANNING;
1432ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        } else if (d.isAvailable()) {
1433ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang            newStatus = d.canConnect() ?
1434ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                    RouteInfo.STATUS_AVAILABLE: RouteInfo.STATUS_IN_USE;
1435705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        } else {
1436705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
1437705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1438705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1439ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        if (d.equals(wfdStatus.getActiveDisplay())) {
1440ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang            final int activeState = wfdStatus.getActiveDisplayState();
1441705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            switch (activeState) {
1442705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
144369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    newStatus = RouteInfo.STATUS_CONNECTED;
1444705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
1445705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
1446705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    newStatus = RouteInfo.STATUS_CONNECTING;
1447705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
1448705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
1449705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    Log.e(TAG, "Active display is not connected!");
1450705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    break;
1451705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
1452705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1453705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1454ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        return newStatus;
1455ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    }
1456ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang
1457ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    static boolean isWifiDisplayEnabled(WifiDisplay d, WifiDisplayStatus wfdStatus) {
1458ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        return d.isAvailable() && (d.canConnect() || d.equals(wfdStatus.getActiveDisplay()));
1459ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    }
1460ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang
1461ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) {
1462ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
1463ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        newRoute.mDeviceAddress = display.getDeviceAddress();
146469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
146569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                | ROUTE_TYPE_REMOTE_DISPLAY;
1466ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
1467ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
1468ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang
146939ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        newRoute.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
1470ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus);
1471ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        newRoute.mName = display.getFriendlyDisplayName();
1472ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        newRoute.mDescription = sStatic.mResources.getText(
1473ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang                com.android.internal.R.string.wireless_display_route_description);
14745830b0b33618940d65197cec99d697b21908fec8Chong Zhang        newRoute.updatePresentationDisplay();
14759dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        newRoute.mDeviceType = RouteInfo.DEVICE_TYPE_TV;
1476ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        return newRoute;
1477ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    }
1478ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang
1479ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang    private static void updateWifiDisplayRoute(
1480ce468a35b388ca46578934706b38dbae94941643Jeff Brown            RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus,
1481ce468a35b388ca46578934706b38dbae94941643Jeff Brown            boolean disconnected) {
1482ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        boolean changed = false;
14832444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        final String newName = display.getFriendlyDisplayName();
14842444ae7e2b8658a4a90f996e678423558744b4a2Jeff Brown        if (!route.getName().equals(newName)) {
1485705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            route.mName = newName;
1486705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            changed = true;
1487705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1488705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1489ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        boolean enabled = isWifiDisplayEnabled(display, wfdStatus);
1490ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        changed |= route.mEnabled != enabled;
1491ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        route.mEnabled = enabled;
1492705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
149339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        changed |= route.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
1494705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1495705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        if (changed) {
1496705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            dispatchRouteChanged(route);
1497705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1498705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1499ce468a35b388ca46578934706b38dbae94941643Jeff Brown        if ((!enabled || disconnected) && route.isSelected()) {
1500705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            // Oops, no longer available. Reselect the default.
150169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            selectDefaultRouteStatic();
1502705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1503705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
1504705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
150575af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown    private static WifiDisplay findWifiDisplay(WifiDisplay[] displays, String deviceAddress) {
1506705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < displays.length; i++) {
150775af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            final WifiDisplay d = displays[i];
150875af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            if (d.getDeviceAddress().equals(deviceAddress)) {
150975af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown                return d;
1510705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
1511705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1512705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return null;
1513705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
1514705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1515705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
1516705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        final int count = sStatic.mRoutes.size();
1517705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        for (int i = 0; i < count; i++) {
1518705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            final RouteInfo info = sStatic.mRoutes.get(i);
1519705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
1520705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                return info;
1521705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
1522705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1523705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        return null;
15248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
15258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15269a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
15279a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a media route.
15289a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1529b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteInfo {
15309a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
15310d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
153256d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        CharSequence mDescription;
15339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private CharSequence mStatus;
15349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mSupportedTypes;
15359dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        int mDeviceType;
15369a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup mGroup;
15379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory mCategory;
1538ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        Drawable mIcon;
15391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        // playback information
15401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackType = PLAYBACK_TYPE_LOCAL;
15411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
15421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
15431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
15441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackStream = AudioManager.STREAM_MUSIC;
15451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        VolumeCallbackInfo mVcb;
154692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        Display mPresentationDisplay;
15475830b0b33618940d65197cec99d697b21908fec8Chong Zhang        int mPresentationDisplayId = -1;
15489a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1549705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        String mDeviceAddress;
1550705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean mEnabled = true;
1551705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
155269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        // An id by which the route is known to the media router service.
155369b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        // Null if this route only exists as an artifact within this process.
155469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        String mGlobalRouteId;
155569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown
1556705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        // A predetermined connection status that can override mStatus
155739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        private int mRealStatusCode;
155839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        private int mResolvedStatusCode;
1559705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
15602ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_NONE = 0;
15612ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_SCANNING = 1;
15622ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_CONNECTING = 2;
15632ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_AVAILABLE = 3;
15642ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
1565ab87a63997a7dc771acfd0dcd7efda990dc3d5feChong Zhang        /** @hide */ public static final int STATUS_IN_USE = 5;
156669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        /** @hide */ public static final int STATUS_CONNECTED = 6;
1567705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
15689dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        /** @hide */
15699dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
15709dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        @Retention(RetentionPolicy.SOURCE)
15719dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        public @interface DeviceType {}
15729dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang
15739dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        /**
15749dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * The default receiver device type of the route indicating the type is unknown.
15759dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         *
15769dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * @see #getDeviceType
15779dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         */
15789dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        public static final int DEVICE_TYPE_UNKNOWN = 0;
15799dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang
15809dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        /**
15819dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * A receiver device type of the route indicating the presentation of the media is happening
15829dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * on a TV.
15839dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         *
15849dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * @see #getDeviceType
15859dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         */
15869dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        public static final int DEVICE_TYPE_TV = 1;
15879dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang
15889dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        /**
15899dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * A receiver device type of the route indicating the presentation of the media is happening
15909dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * on a speaker.
15919dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         *
15929dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * @see #getDeviceType
15939dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         */
15949dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        public static final int DEVICE_TYPE_SPEAKER = 2;
15959dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang
15969dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        /**
15979dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * A receiver device type of the route indicating the presentation of the media is happening
15989dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * on a bluetooth device such as a bluetooth speaker.
15999dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         *
16009dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * @see #getDeviceType
16019dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         */
16029dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        public static final int DEVICE_TYPE_BLUETOOTH = 3;
16039dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang
1604b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        private Object mTag;
1605b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
16067c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        /** @hide */
16077c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        @IntDef({PLAYBACK_TYPE_LOCAL, PLAYBACK_TYPE_REMOTE})
16087c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        @Retention(RetentionPolicy.SOURCE)
16097c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        public @interface PlaybackType {}
16107c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang
16111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * The default playback type, "local", indicating the presentation of the media is happening
1613bc4cf00dc5bf6e0e3c01206d5c46e64306df260aJohn Spurlock         * on the same device (e&#46;g&#46; a phone, a tablet) as where it is controlled from.
161469b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown         * @see #getPlaybackType()
16151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_LOCAL = 0;
16177c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang
16181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * A playback type indicating the presentation of the media is happening on
1620bc4cf00dc5bf6e0e3c01206d5c46e64306df260aJohn Spurlock         * a different device (i&#46;e&#46; the remote device) than where it is controlled from.
162169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown         * @see #getPlaybackType()
16221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_REMOTE = 1;
16247c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang
16257c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        /** @hide */
16267c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang         @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
16277c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang         @Retention(RetentionPolicy.SOURCE)
16287c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang         private @interface PlaybackVolume {}
16297c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang
16301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
1631bc4cf00dc5bf6e0e3c01206d5c46e64306df260aJohn Spurlock         * Playback information indicating the playback volume is fixed, i&#46;e&#46; it cannot be
16321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * controlled from this object. An example of fixed playback volume is a remote player,
16331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
16341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * than attenuate at the source.
163569b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown         * @see #getVolumeHandling()
16361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_FIXED = 0;
16381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is variable and can be controlled
16401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * from this object.
164169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown         * @see #getVolumeHandling()
16421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_VARIABLE = 1;
16441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16459a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteInfo(RouteCategory category) {
16469a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mCategory = category;
16479dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang            mDeviceType = DEVICE_TYPE_UNKNOWN;
16489a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16499a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16509a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
165156d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * Gets the user-visible name of the route.
165256d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * <p>
165356d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * The route name identifies the destination represented by the route.
165456d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * It may be a user-supplied name, an alias, or device serial number.
165556d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * </p>
165656d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         *
165756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * @return The user-visible name of a media route.  This is the string presented
16589a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * to users who may select this as the active route.
16599a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
16609a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
16610d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
16620d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
166356d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown
16640d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
166556d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * Return the properly localized/resource user-visible name of this route.
166656d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * <p>
166756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * The route name identifies the destination represented by the route.
166856d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * It may be a user-supplied name, an alias, or device serial number.
166956d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * </p>
167056d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         *
16710d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context used to resolve the correct configuration to load
167256d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * @return The user-visible name of a media route.  This is the string presented
16730d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * to users who may select this as the active route.
16740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
16750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
16760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
16770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
167856d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown
16790d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
16800d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
1681eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                return res.getText(mNameResId);
16820d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
16839a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
16849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16859a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16869a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
168756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * Gets the user-visible description of the route.
168856d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * <p>
168956d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * The route description describes the kind of destination represented by the route.
169056d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * It may be a user-supplied string, a model number or brand of device.
169156d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * </p>
169256d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         *
169356d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * @return The description of the route, or null if none.
169456d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         */
169556d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        public CharSequence getDescription() {
169656d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown            return mDescription;
169756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        }
169856d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown
169956d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        /**
170056d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * @return The user-visible status for a media route. This may include a description
17019a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of the currently playing media, if available.
17029a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
17039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getStatus() {
17049a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mStatus;
17059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17069a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
1708705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * Set this route's status by predetermined status code. If the caller
1709705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * should dispatch a route changed event this call will return true;
1710705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
171139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        boolean setRealStatusCode(int statusCode) {
171239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            if (mRealStatusCode != statusCode) {
171339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                mRealStatusCode = statusCode;
171439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                return resolveStatusCode();
171539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            }
171639ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            return false;
171739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        }
171839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown
171939ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        /**
172039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown         * Resolves the status code whenever the real status code or selection state
172139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown         * changes.
172239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown         */
172339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown        boolean resolveStatusCode() {
172439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            int statusCode = mRealStatusCode;
172539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            if (isSelected()) {
1726705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                switch (statusCode) {
172739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    // If the route is selected and its status appears to be between states
172839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    // then report it as connecting even though it has not yet had a chance
172939ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    // to officially move into the CONNECTING state.  Note that routes in
173039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    // the NONE state are assumed to not require an explicit connection
173139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    // lifecycle whereas those that are AVAILABLE are assumed to have
173239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    // to eventually proceed to CONNECTED.
1733705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                    case STATUS_AVAILABLE:
173439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    case STATUS_SCANNING:
173539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                        statusCode = STATUS_CONNECTING;
173669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                        break;
1737705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                }
1738705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
173939ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            if (mResolvedStatusCode == statusCode) {
174039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                return false;
174139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            }
174239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown
174339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            mResolvedStatusCode = statusCode;
174439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            int resId;
174539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            switch (statusCode) {
174639ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_SCANNING:
174739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    resId = com.android.internal.R.string.media_route_status_scanning;
174839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    break;
174939ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_CONNECTING:
175039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    resId = com.android.internal.R.string.media_route_status_connecting;
175139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    break;
175239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_AVAILABLE:
175339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    resId = com.android.internal.R.string.media_route_status_available;
175439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    break;
175539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_NOT_AVAILABLE:
175639ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    resId = com.android.internal.R.string.media_route_status_not_available;
175739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    break;
175839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_IN_USE:
175939ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    resId = com.android.internal.R.string.media_route_status_in_use;
176039ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    break;
176139ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_CONNECTED:
176239ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                case STATUS_NONE:
176339ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                default:
176439ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    resId = 0;
176539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown                    break;
176639ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            }
176739ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
176839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            return true;
1769705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
1770705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
1771705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
17722ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell         * @hide
17732ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell         */
17742ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        public int getStatusCode() {
177539ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            return mResolvedStatusCode;
17762ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        }
17772ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell
17782ee6a2a83262d05a566bd713d238e89edfd33a29Adam Powell        /**
17799a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return A media type flag set describing which types this route supports.
17809a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
17819a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
17829a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mSupportedTypes;
17839a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
17849a1de308cea2d160778fd977825f10a07b49d738Adam Powell
17859dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        /**
17869dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * Gets the type of the receiver device associated with this route.
17879dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         *
17889dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * @return The type of the receiver device associated with this route:
17899dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * {@link #DEVICE_TYPE_BLUETOOTH}, {@link #DEVICE_TYPE_TV}, {@link #DEVICE_TYPE_SPEAKER},
17909dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         * or {@link #DEVICE_TYPE_UNKNOWN}.
17919dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang         */
17929dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        @DeviceType
17939dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        public int getDeviceType() {
17949dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang            return mDeviceType;
17959dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang        }
17969dd66417ea984e4ff809ecdba78e3f55a8a17467Insun Kang
17970abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        /** @hide */
17980abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        public boolean matchesTypes(int types) {
17990abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            return (mSupportedTypes & types) != 0;
18000abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        }
18010abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
18029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18039a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The group that this route belongs to.
18049a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteGroup getGroup() {
18069a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroup;
18079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18089a1de308cea2d160778fd977825f10a07b49d738Adam Powell
18099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
18109a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the category this route belongs to.
18119a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
18129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteCategory getCategory() {
18139a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mCategory;
18149a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
18159a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1816ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1817ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Get the icon representing this route.
1818ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * This icon will be used in picker UIs if available.
1819ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1820ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @return the icon representing this route or null if no icon is available
1821ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1822ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public Drawable getIconDrawable() {
1823ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            return mIcon;
1824ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1825ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1826b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
1827b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * Set an application-specific tag object for this route.
1828b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * The application may use this to store arbitrary data associated with the
1829b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * route for internal tracking.
1830b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
1831b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * <p>Note that the lifespan of a route may be well past the lifespan of
1832b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * an Activity or other Context; take care that objects you store here
1833b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * will not keep more data in memory alive than you intend.</p>
1834b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
1835b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @param tag Arbitrary, app-specific data for this route to hold for later use
1836b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
1837b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public void setTag(Object tag) {
1838b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            mTag = tag;
1839130b4572d1f3df702e5b296a655d15a41f6d4c66Adam Powell            routeUpdated();
1840b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
1841b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
1842b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
1843b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @return The tag object previously set by the application
1844b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @see #setTag(Object)
1845b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
1846b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public Object getTag() {
1847b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            return mTag;
1848b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
1849b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
18501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
18511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the type of playback associated with this route
18521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackType(int)
18531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
18547c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        @PlaybackType
18551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackType() {
18561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackType;
18571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
18581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
18591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
18601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the stream over which the playback associated with this route is performed
18611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackStream(int)
18621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
18631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackStream() {
18641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackStream;
18651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
18661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
18671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
18688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Return the current volume for this route. Depending on the route, this may only
18698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * be valid if the route is currently selected.
18708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
18711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the volume at which the playback associated with this route is performed
18721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolume(int)
18731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
18741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolume() {
18751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
18761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int vol = 0;
18771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
18781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream);
18791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
18801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
18811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
18821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return vol;
18831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
18841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolume;
18851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
18861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
18871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
18881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
18898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request a volume change for this route.
18908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param volume value between 0 and getVolumeMax
18918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
18928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
18938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
18948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
1895ba50b97cff80e73620a0e3d13cae169e095974a7Dianne Hackborn                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0,
1896ba50b97cff80e73620a0e3d13cae169e095974a7Dianne Hackborn                            ActivityThread.currentPackageName());
18978e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
18988e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
18998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
19008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
190169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                sStatic.requestSetVolume(this, volume);
19028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
19038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
19048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
19058e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
19068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request an incremental volume update for this route.
19078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param direction Delta to apply to the current volume
19088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
19098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
19108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
19118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
19128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    final int volume =
19138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                            Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
1914ba50b97cff80e73620a0e3d13cae169e095974a7Dianne Hackborn                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0,
1915ba50b97cff80e73620a0e3d13cae169e095974a7Dianne Hackborn                            ActivityThread.currentPackageName());
19168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
19178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
19188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
19198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
192069b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                sStatic.requestUpdateVolume(this, direction);
19218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
19228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
19238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
19248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
19251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the maximum volume at which the playback associated with this route is performed
19261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeMax(int)
19271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
19281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeMax() {
19291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
19301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int volMax = 0;
19311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
19321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    volMax = sStatic.mAudioService.getStreamMaxVolume(mPlaybackStream);
19331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
19341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
19351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
19361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return volMax;
19371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
19381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolumeMax;
19391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
19401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
19411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
19421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
19431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return how volume is handling on the route
19441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeHandling(int)
19451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
19467c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        @PlaybackVolume
19471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeHandling() {
19481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mVolumeHandling;
19491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
19501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
1951705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
195292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * Gets the {@link Display} that should be used by the application to show
195392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * a {@link android.app.Presentation} on an external display when this route is selected.
195492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * Depending on the route, this may only be valid if the route is currently
195592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * selected.
195692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * <p>
195792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * The preferred presentation display may change independently of the route
195892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * being selected or unselected.  For example, the presentation display
195992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * of the default system route may change when an external HDMI display is connected
196092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * or disconnected even though the route itself has not changed.
196192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p><p>
196292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * This method may return null if there is no external display associated with
196392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * the route or if the display is not ready to show UI yet.
196492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p><p>
196592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * The application should listen for changes to the presentation display
196692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
196792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * show or dismiss its {@link android.app.Presentation} accordingly when the display
196892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * becomes available or is removed.
196992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p><p>
197092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * This method only makes sense for {@link #ROUTE_TYPE_LIVE_VIDEO live video} routes.
197192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p>
197292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
197392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @return The preferred presentation display to use when this route is
197492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * selected or null if none.
197592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
197692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @see #ROUTE_TYPE_LIVE_VIDEO
197792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @see android.app.Presentation
197892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         */
197992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public Display getPresentationDisplay() {
198092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            return mPresentationDisplay;
198192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
198292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
19835830b0b33618940d65197cec99d697b21908fec8Chong Zhang        boolean updatePresentationDisplay() {
19845830b0b33618940d65197cec99d697b21908fec8Chong Zhang            Display display = choosePresentationDisplay();
19855830b0b33618940d65197cec99d697b21908fec8Chong Zhang            if (mPresentationDisplay != display) {
19865830b0b33618940d65197cec99d697b21908fec8Chong Zhang                mPresentationDisplay = display;
19875830b0b33618940d65197cec99d697b21908fec8Chong Zhang                return true;
19885830b0b33618940d65197cec99d697b21908fec8Chong Zhang            }
19895830b0b33618940d65197cec99d697b21908fec8Chong Zhang            return false;
19905830b0b33618940d65197cec99d697b21908fec8Chong Zhang        }
19915830b0b33618940d65197cec99d697b21908fec8Chong Zhang
19925830b0b33618940d65197cec99d697b21908fec8Chong Zhang        private Display choosePresentationDisplay() {
19935830b0b33618940d65197cec99d697b21908fec8Chong Zhang            if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
19945830b0b33618940d65197cec99d697b21908fec8Chong Zhang                Display[] displays = sStatic.getAllPresentationDisplays();
19955830b0b33618940d65197cec99d697b21908fec8Chong Zhang
19965830b0b33618940d65197cec99d697b21908fec8Chong Zhang                // Ensure that the specified display is valid for presentations.
19975830b0b33618940d65197cec99d697b21908fec8Chong Zhang                // This check will normally disallow the default display unless it was
19985830b0b33618940d65197cec99d697b21908fec8Chong Zhang                // configured as a presentation display for some reason.
19995830b0b33618940d65197cec99d697b21908fec8Chong Zhang                if (mPresentationDisplayId >= 0) {
20005830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    for (Display display : displays) {
20015830b0b33618940d65197cec99d697b21908fec8Chong Zhang                        if (display.getDisplayId() == mPresentationDisplayId) {
20025830b0b33618940d65197cec99d697b21908fec8Chong Zhang                            return display;
20035830b0b33618940d65197cec99d697b21908fec8Chong Zhang                        }
20045830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    }
20055830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    return null;
20065830b0b33618940d65197cec99d697b21908fec8Chong Zhang                }
20075830b0b33618940d65197cec99d697b21908fec8Chong Zhang
20085830b0b33618940d65197cec99d697b21908fec8Chong Zhang                // Find the indicated Wifi display by its address.
20095830b0b33618940d65197cec99d697b21908fec8Chong Zhang                if (mDeviceAddress != null) {
20105830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    for (Display display : displays) {
20115830b0b33618940d65197cec99d697b21908fec8Chong Zhang                        if (display.getType() == Display.TYPE_WIFI
20125830b0b33618940d65197cec99d697b21908fec8Chong Zhang                                && mDeviceAddress.equals(display.getAddress())) {
20135830b0b33618940d65197cec99d697b21908fec8Chong Zhang                            return display;
20145830b0b33618940d65197cec99d697b21908fec8Chong Zhang                        }
20155830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    }
20165830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    return null;
20175830b0b33618940d65197cec99d697b21908fec8Chong Zhang                }
20185830b0b33618940d65197cec99d697b21908fec8Chong Zhang
20195830b0b33618940d65197cec99d697b21908fec8Chong Zhang                // For the default route, choose the first presentation display from the list.
20205830b0b33618940d65197cec99d697b21908fec8Chong Zhang                if (this == sStatic.mDefaultAudioVideo && displays.length > 0) {
20215830b0b33618940d65197cec99d697b21908fec8Chong Zhang                    return displays[0];
20225830b0b33618940d65197cec99d697b21908fec8Chong Zhang                }
20235830b0b33618940d65197cec99d697b21908fec8Chong Zhang            }
20245830b0b33618940d65197cec99d697b21908fec8Chong Zhang            return null;
20255830b0b33618940d65197cec99d697b21908fec8Chong Zhang        }
20265830b0b33618940d65197cec99d697b21908fec8Chong Zhang
202775af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        /** @hide */
202875af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        public String getDeviceAddress() {
202975af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown            return mDeviceAddress;
203075af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown        }
203175af171006ee80aaf7cfb56ded1378afe0084ccaJeff Brown
203292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        /**
2033a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         * Returns true if this route is enabled and may be selected.
2034a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         *
2035a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         * @return True if this route is enabled.
2036705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
2037705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public boolean isEnabled() {
2038705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return mEnabled;
2039705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
2040705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
2041a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown        /**
2042a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         * Returns true if the route is in the process of connecting and is not
2043a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         * yet ready for use.
2044a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         *
2045a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         * @return True if this route is in the process of connecting.
2046a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown         */
2047a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown        public boolean isConnecting() {
204839ad0e559896b45185429ea17cd12f18f7ae842cJeff Brown            return mResolvedStatusCode == STATUS_CONNECTING;
20490abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        }
20500abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
20510abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        /** @hide */
20520abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        public boolean isSelected() {
20530abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            return this == sStatic.mSelectedRoute;
20540abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        }
20550abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
20560abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        /** @hide */
20570abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        public boolean isDefault() {
20580abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            return this == sStatic.mDefaultAudioVideo;
20590abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        }
20600abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown
20610abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        /** @hide */
206259579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim        public boolean isBluetooth() {
206359579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim            return this == sStatic.mBluetoothA2dpRoute;
206459579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim        }
206559579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim
206659579ce59d6e2c42e6ea1b389ba7e228aaa9122fSungsoo Lim        /** @hide */
20670abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown        public void select() {
20680abd3a6ce83ed23abe614155e776b600ef2a66c3Jeff Brown            selectRouteStatic(mSupportedTypes, this, true);
2069a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown        }
2070a27b8fb4296c1271ddf5916843ddffda6764e65fJeff Brown
20719a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void setStatusInt(CharSequence status) {
20729a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!status.equals(mStatus)) {
20739a1de308cea2d160778fd977825f10a07b49d738Adam Powell                mStatus = status;
20749a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (mGroup != null) {
20759a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    mGroup.memberStatusChanged(this, status);
20769a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
20779a1de308cea2d160778fd977825f10a07b49d738Adam Powell                routeUpdated();
20789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
20799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
20809a1de308cea2d160778fd977825f10a07b49d738Adam Powell
20811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
208269b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown            @Override
20831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
20841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                sStatic.mHandler.post(new Runnable() {
20851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    @Override
20861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    public void run() {
20871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        if (mVcb != null) {
20881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            if (direction != 0) {
20891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
20901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            } else {
20911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeSetRequest(mVcb.route, value);
20921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            }
20931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        }
20941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    }
20951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                });
20961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
20971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        };
20981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
20999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void routeUpdated() {
21009a1de308cea2d160778fd977825f10a07b49d738Adam Powell            updateRoute(this);
21019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
21029a1de308cea2d160778fd977825f10a07b49d738Adam Powell
21039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
21049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
2105d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            String supportedTypes = typesToString(getSupportedTypes());
210692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown            return getClass().getSimpleName() + "{ name=" + getName() +
210756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown                    ", description=" + getDescription() +
210892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", status=" + getStatus() +
210992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", category=" + getCategory() +
211092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown                    ", supportedTypes=" + supportedTypes +
211169b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                    ", presentationDisplay=" + mPresentationDisplay + " }";
21129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
21139a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
21149a1de308cea2d160778fd977825f10a07b49d738Adam Powell
21159a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
21169a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that the application may define and modify.
21178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
21188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
21199a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
21209a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter.RouteInfo
21219a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2122b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class UserRouteInfo extends RouteInfo {
2123ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        RemoteControlClient mRcc;
21245d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik        SessionVolumeProvider mSvp;
21259a1de308cea2d160778fd977825f10a07b49d738Adam Powell
21269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        UserRouteInfo(RouteCategory category) {
21279a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
21289a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mSupportedTypes = ROUTE_TYPE_USER;
21298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = PLAYBACK_TYPE_REMOTE;
21308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
21319a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
21329a1de308cea2d160778fd977825f10a07b49d738Adam Powell
21339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
21349a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the user-visible name of this route.
21359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param name Name to display to the user to describe this route
21369a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
21379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setName(CharSequence name) {
2138eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon            mNameResId = 0;
21399a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
21409a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
21419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
21425d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
21430d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
21440d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Set the user-visible name of this route.
214556d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * <p>
214656d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * The route name identifies the destination represented by the route.
214756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * It may be a user-supplied name, an alias, or device serial number.
214856d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * </p>
214956d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         *
21500d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param resId Resource ID of the name to display to the user to describe this route
21510d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
21520d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public void setName(int resId) {
21530d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = resId;
21540d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mName = null;
21550d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            routeUpdated();
21560d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
21579a1de308cea2d160778fd977825f10a07b49d738Adam Powell
21589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
215956d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * Set the user-visible description of this route.
216056d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * <p>
216156d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * The route description describes the kind of destination represented by the route.
216256d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * It may be a user-supplied string, a model number or brand of device.
216356d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * </p>
216456d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         *
216556d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         * @param description The description of the route, or null if none.
216656d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown         */
216756d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        public void setDescription(CharSequence description) {
216856d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown            mDescription = description;
216956d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown            routeUpdated();
217056d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        }
217156d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown
217256d4b744b374af11f540567d2b43d66d8d7c301eJeff Brown        /**
21739a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the current user-visible status for this route.
21749a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param status Status to display to the user to describe what the endpoint
21759a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of this route is currently doing
21769a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
21779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setStatus(CharSequence status) {
21789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
21799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2180ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
2181ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
2182ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set the RemoteControlClient responsible for reporting playback info for this
2183ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * user route.
2184ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
2185ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * <p>If this route manages remote playback, the data exposed by this
2186ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * RemoteControlClient will be used to reflect and update information
2187ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * such as route volume info in related UIs.</p>
2188ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
21891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * <p>The RemoteControlClient must have been previously registered with
21901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.</p>
21911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *
2192ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param rcc RemoteControlClient associated with this route
2193ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
2194ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setRemoteControlClient(RemoteControlClient rcc) {
2195ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mRcc = rcc;
21961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            updatePlaybackInfoOnRcc();
2197ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
2198ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
2199ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
22004599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * Retrieve the RemoteControlClient associated with this route, if one has been set.
22014599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         *
22024599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @return the RemoteControlClient associated with this route
22034599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @see #setRemoteControlClient(RemoteControlClient)
22044599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         */
22054599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        public RemoteControlClient getRemoteControlClient() {
22064599696591f745b3a546197d2ba7e5cfc5562484Adam Powell            return mRcc;
22074599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        }
22084599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
22094599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        /**
2210ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
2211ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
2212ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
2213ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this route
2214ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
2215ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
2216ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
2217ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
2218ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
2219ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
2220ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
2221ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
2222ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
222371c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this route
2224ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
22257b9c912f536925ac6ec43935d6e97506851b33d6Tor Norbye        public void setIconResource(@DrawableRes int resId) {
2226ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
2227ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
22281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
22291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
22301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Set a callback to be notified of volume update requests
22311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param vcb
22321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
22331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeCallback(VolumeCallback vcb) {
22341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            mVcb = new VolumeCallbackInfo(vcb, this);
22351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
22361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
22371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
22381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether playback associated with this route is "local"
22391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_LOCAL}) or "remote"
22401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_REMOTE}).
22411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param type
22421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
22437c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        public void setPlaybackType(@RouteInfo.PlaybackType int type) {
22441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType != type) {
22451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackType = type;
2246430fc48865e5a371b08f180390946b96d73848feRoboErik                configureSessionVolume();
22471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
22481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
22491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
22501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
22511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether volume for the playback associated with this route is fixed
22521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_FIXED}) or can modified
22531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_VARIABLE}).
22541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeHandling
22551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
22567c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang        public void setVolumeHandling(@RouteInfo.PlaybackVolume int volumeHandling) {
22571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeHandling != volumeHandling) {
22581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeHandling = volumeHandling;
2259430fc48865e5a371b08f180390946b96d73848feRoboErik                configureSessionVolume();
22601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
22611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
22621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
22631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
22641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines at what volume the playback associated with this route is performed (for user
22651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * feedback purposes). This information is only used when the playback is not local.
22661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume
22671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
22681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolume(int volume) {
22698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            volume = Math.max(0, Math.min(volume, getVolumeMax()));
22701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolume != volume) {
22711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolume = volume;
2272430fc48865e5a371b08f180390946b96d73848feRoboErik                if (mSvp != null) {
22730d0f67f5ee5f939a1b611bc4583212707afd9beeRoboErik                    mSvp.setCurrentVolume(mVolume);
22745d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                }
22758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
2276f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (mGroup != null) {
2277f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    mGroup.memberVolumeChanged(this);
2278f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
22798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
22808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
22818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
22828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
22838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
22848e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
22858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
22868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
22878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
22888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
22898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeSetRequest(this, volume);
22908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
22918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
22928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
22938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
22948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
22958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
22968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
22978e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
22988e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
22998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
23008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeUpdateRequest(this, direction);
23011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
23021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
23031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
23041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
23051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines the maximum volume at which the playback associated with this route is performed
23061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * (for user feedback purposes). This information is only used when the playback is not
23071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * local.
23081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeMax
23091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
23101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeMax(int volumeMax) {
23111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeMax != volumeMax) {
23121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeMax = volumeMax;
2313430fc48865e5a371b08f180390946b96d73848feRoboErik                configureSessionVolume();
23141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
23151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
23161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
23171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
23181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines over what stream type the media is presented.
23191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param stream
23201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
23211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackStream(int stream) {
23221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackStream != stream) {
23231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackStream = stream;
2324430fc48865e5a371b08f180390946b96d73848feRoboErik                configureSessionVolume();
23251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
23261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
23271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
23281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void updatePlaybackInfoOnRcc() {
2329430fc48865e5a371b08f180390946b96d73848feRoboErik            configureSessionVolume();
23305d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik        }
23315d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
23325d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik        private void configureSessionVolume() {
23335d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            if (mRcc == null) {
23345d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                if (DEBUG) {
2335eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                    Log.d(TAG, "No Rcc to configure volume for route " + getName());
23365d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                }
23375d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                return;
23385d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            }
23395d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            MediaSession session = mRcc.getMediaSession();
23405d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            if (session == null) {
23415d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                if (DEBUG) {
23425d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    Log.d(TAG, "Rcc has no session to configure volume");
23435d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                }
23445d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                return;
23455d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            }
23465d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            if (mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
23477c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang                @VolumeProvider.ControlType int volumeControl =
23487c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang                        VolumeProvider.VOLUME_CONTROL_FIXED;
23495d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                switch (mVolumeHandling) {
23505d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    case RemoteControlClient.PLAYBACK_VOLUME_VARIABLE:
2351ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik                        volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
23525d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        break;
23535d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    case RemoteControlClient.PLAYBACK_VOLUME_FIXED:
23545d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    default:
23555d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        break;
23561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
23575d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                // Only register a new listener if necessary
23585d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                if (mSvp == null || mSvp.getVolumeControl() != volumeControl
23595d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        || mSvp.getMaxVolume() != mVolumeMax) {
23600d0f67f5ee5f939a1b611bc4583212707afd9beeRoboErik                    mSvp = new SessionVolumeProvider(volumeControl, mVolumeMax, mVolume);
23615d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    session.setPlaybackToRemote(mSvp);
23625d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                }
23635d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            } else {
23645d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                // We only know how to handle local and remote, fall back to local if not remote.
23659db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik                AudioAttributes.Builder bob = new AudioAttributes.Builder();
23669db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik                bob.setLegacyStreamType(mPlaybackStream);
23679db9bf7034d7dcdf596dc22d521b18975d0dd2b9RoboErik                session.setPlaybackToLocal(bob.build());
23685d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                mSvp = null;
23691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
23701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
23711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
2372ef3c9e9b057a5aac2d0d012e8e6385660478e203RoboErik        class SessionVolumeProvider extends VolumeProvider {
23735d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
23747c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang            public SessionVolumeProvider(@VolumeProvider.ControlType int volumeControl,
23757c090d54e2c0eb5309d3f7dc131e137d9c986793Insun Kang                    int maxVolume, int currentVolume) {
23760d0f67f5ee5f939a1b611bc4583212707afd9beeRoboErik                super(volumeControl, maxVolume, currentVolume);
23775d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            }
23785d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
23795d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            @Override
23805d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            public void onSetVolumeTo(final int volume) {
23815d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                sStatic.mHandler.post(new Runnable() {
23825d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    @Override
23835d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    public void run() {
23845d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        if (mVcb != null) {
23855d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                            mVcb.vcb.onVolumeSetRequest(mVcb.route, volume);
23865d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        }
23875d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    }
23885d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                });
23895d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            }
23905d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
23915d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            @Override
23921ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik            public void onAdjustVolume(final int direction) {
23935d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                sStatic.mHandler.post(new Runnable() {
23945d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    @Override
23955d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    public void run() {
23965d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        if (mVcb != null) {
23971ff5b1648a051e9650614f0c0f1b3f449777db81RoboErik                            mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
23985d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                        }
23995d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                    }
24005d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik                });
24015d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik            }
24025d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik        }
24039a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
24049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
24059a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
24069a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that consists of multiple other routes in a group.
24079a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2408b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteGroup extends RouteInfo {
24099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
24109a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private boolean mUpdateName;
24119a1de308cea2d160778fd977825f10a07b49d738Adam Powell
24129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup(RouteCategory category) {
24139a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
24149a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroup = this;
24158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
24169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
24179a1de308cea2d160778fd977825f10a07b49d738Adam Powell
241869b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        @Override
24190d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
24209a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (mUpdateName) updateName();
24210d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return super.getName(res);
24229a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
24239a1de308cea2d160778fd977825f10a07b49d738Adam Powell
24249a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
24259a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group. The route must not currently belong to another group.
24269a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
24279a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add to this group
24289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
24299a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route) {
24309a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
24319a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
24329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
24339a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
24349a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
24359a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
24369a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
24379a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
24389a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
2439d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            final int at = mRoutes.size();
24409a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(route);
2441d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
24429a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
2443f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
24449a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
2445f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, at);
24469a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
24479a1de308cea2d160778fd977825f10a07b49d738Adam Powell
24489a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
24499a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group before the specified index.
24509a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
24519a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add
24529a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param insertAt insert the new route before this index
24539a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
24549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route, int insertAt) {
24559a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
24569a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
24579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
24589a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
24599a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
24609a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
24619a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
24629a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
24639a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
24649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(insertAt, route);
2465d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
24669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
2467f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
24689a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
2469f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, insertAt);
24709a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
24719a1de308cea2d160778fd977825f10a07b49d738Adam Powell
24729a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
24739a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove a route from this group.
24749a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
24759a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to remove
24769a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
24779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(RouteInfo route) {
24789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != this) {
24799a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException("Route " + route +
24809a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        " is not a member of this group.");
24819a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
24829a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.remove(route);
2483d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
24849a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
2485f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
2486d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
24879a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
24889a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
24899a1de308cea2d160778fd977825f10a07b49d738Adam Powell
24909a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
24919a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove the route at the specified index from this group.
24929a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
24939a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param index index of the route to remove
24949a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
24959a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(int index) {
2496d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            RouteInfo route = mRoutes.remove(index);
2497d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
24989a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
2499f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
2500d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
25019a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
25029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
25039a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2504d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
2505d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The number of routes in this group
2506d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
2507d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public int getRouteCount() {
2508d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.size();
2509d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
2510d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
2511d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
2512d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the route in this group at the specified index
2513d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
2514d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index Index to fetch
2515d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The route at index
2516d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
2517d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public RouteInfo getRouteAt(int index) {
2518d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.get(index);
2519d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
2520d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
2521ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
2522ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
2523ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
2524ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
2525ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this group
2526ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
2527ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
2528ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
2529ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
2530ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
2531ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
2532ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
2533ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
2534ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
253571c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this group
2536ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
25377b9c912f536925ac6ec43935d6e97506851b33d6Tor Norbye        public void setIconResource(@DrawableRes int resId) {
2538ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
2539ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
2540ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
25418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
25428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
25438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
25448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
25458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
25468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
25478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
25488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final float scaledVolume = (float) volume / maxVol;
25498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
25508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
25518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
25528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeVol = (int) (scaledVolume * route.getVolumeMax());
25538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestSetVolume(routeVol);
25548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
25558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
25568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
25578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
25588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
25598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
25608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
25618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
25628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
25638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
25648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
25658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
25668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
25678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
25688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
2569f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
25708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
25718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
25728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestUpdateVolume(direction);
2573f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = route.getVolume();
2574f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
2575f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
2576f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
25778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
25788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
25798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
25808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
25818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
25828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
25838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
25849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberNameChanged(RouteInfo info, CharSequence name) {
25859a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
25869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
25879a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
25889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
25899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberStatusChanged(RouteInfo info, CharSequence status) {
25909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
25919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
25929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2593f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void memberVolumeChanged(RouteInfo info) {
2594f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
2595f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
2596f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
2597f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void updateVolume() {
2598f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            // A group always represents the highest component volume value.
2599f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            final int routeCount = getRouteCount();
2600f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
2601f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            for (int i = 0; i < routeCount; i++) {
2602f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = getRouteAt(i).getVolume();
2603f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
2604f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
2605f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
2606f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
2607f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            if (volume != mVolume) {
2608f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                mVolume = volume;
2609f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                dispatchRouteVolumeChanged(this);
2610f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
2611f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
2612f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
2613d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
2614d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        void routeUpdated() {
2615d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            int types = 0;
2616d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
2617b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            if (count == 0) {
2618b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // Don't keep empty groups in the router.
261969b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown                MediaRouter.removeRouteStatic(this);
2620b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                return;
2621b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            }
2622b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
26238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            int maxVolume = 0;
26248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isLocal = true;
26258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isFixedVolume = true;
2626d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
26278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = mRoutes.get(i);
26288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                types |= route.mSupportedTypes;
26298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeMaxVolume = route.getVolumeMax();
26308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (routeMaxVolume > maxVolume) {
26318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    maxVolume = routeMaxVolume;
26328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
26338e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
26348e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
2635d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
26368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
26378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
2638d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mSupportedTypes = types;
26398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeMax = maxVolume;
2640d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
2641d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            super.routeUpdated();
2642d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
2643d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
26449a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void updateName() {
26459a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final StringBuilder sb = new StringBuilder();
26469a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final int count = mRoutes.size();
26479a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
26489a1de308cea2d160778fd977825f10a07b49d738Adam Powell                final RouteInfo info = mRoutes.get(i);
2649b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // TODO: There's probably a much more correct way to localize this.
2650eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                if (i > 0) {
2651eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                    sb.append(", ");
2652eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                }
2653eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon                sb.append(info.getName());
26549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
26559a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = sb.toString();
26569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = false;
26579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2658d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
2659d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
2660d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        public String toString() {
2661d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            StringBuilder sb = new StringBuilder(super.toString());
2662d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append('[');
2663d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
2664d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
2665d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                if (i > 0) sb.append(", ");
2666d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                sb.append(mRoutes.get(i));
2667d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
2668d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append(']');
2669d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return sb.toString();
2670d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
26719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
26729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
26739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
26749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Definition of a category of routes. All routes belong to a category.
26759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2676b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteCategory {
26779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
26780d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
26799a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mTypes;
26809a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final boolean mGroupable;
2681705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        boolean mIsSystem;
26829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
26839a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteCategory(CharSequence name, int types, boolean groupable) {
26849a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
26859a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mTypes = types;
26869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroupable = groupable;
26879a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
26889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
26890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        RouteCategory(int nameResId, int types, boolean groupable) {
26900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = nameResId;
26910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mTypes = types;
26920d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mGroupable = groupable;
26930d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
26940d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
26959a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
26969a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the name of this route category
26979a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
26989a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
26990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
27000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
27015d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
27020d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
27030d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/configuration dependent name of this RouteCategory.
27045d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik         *
27050d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context to resolve name resources
27060d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return the name of this route category
27070d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
27080d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
27090d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
27100d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
27115d3114b64a88ac1f72becd8d46f148c666f64aa3RoboErik
27120d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
27130d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
27140d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return res.getText(mNameResId);
27150d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
27169a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
27179a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
27189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
27199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
2720d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the current list of routes in this category that have been added
2721d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * to the MediaRouter.
27229a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
2723d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * <p>This list will not include routes that are nested within RouteGroups.
2724d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * A RouteGroup is treated as a single route within its category.</p>
2725d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
2726d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param out a List to fill with the routes in this category. If this parameter is
2727d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            non-null, it will be cleared, filled with the current routes with this
2728d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            category, and returned. If this parameter is null, a new List will be
2729d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            allocated to report the category's current routes.
2730d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return A list with the routes in this category that have been added to the MediaRouter.
27319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
2732d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public List<RouteInfo> getRoutes(List<RouteInfo> out) {
2733d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if (out == null) {
2734d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out = new ArrayList<RouteInfo>();
2735d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            } else {
2736d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out.clear();
2737d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
2738d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
2739b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = getRouteCountStatic();
2740d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            for (int i = 0; i < count; i++) {
2741b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteInfo route = getRouteAtStatic(i);
2742d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                if (route.mCategory == this) {
2743d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                    out.add(route);
2744d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                }
2745d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
2746d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return out;
27479a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
27489a1de308cea2d160778fd977825f10a07b49d738Adam Powell
27499a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
27509a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return Flag set describing the route types supported by this category
27519a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
27529a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
27539a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mTypes;
27549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
27559a1de308cea2d160778fd977825f10a07b49d738Adam Powell
27569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
27579a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Return whether or not this category supports grouping.
27589a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
27599a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>If this method returns true, all routes obtained from this category
2760d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
27619a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
27629a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return true if this category supports
27639a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
27649a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public boolean isGroupable() {
27659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroupable;
27669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
27679a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2768705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        /**
2769705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @return true if this is the category reserved for system routes.
2770705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         * @hide
2771705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell         */
2772705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public boolean isSystem() {
2773705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            return mIsSystem;
2774705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
2775705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell
277669b07161bebdb2c726e3a826c2268866f1a94517Jeff Brown        @Override
27779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
2778eadd6d2329972129c96dee390a8b786b37313d17Hyundo Moon            return "RouteCategory{ name=" + getName() + " types=" + typesToString(mTypes) +
2779d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " groupable=" + mGroupable + " }";
27809a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
27819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
27829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
27839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static class CallbackInfo {
27849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int type;
278566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        public int flags;
2786b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final Callback cb;
2787b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final MediaRouter router;
27889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
278966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        public CallbackInfo(Callback cb, int type, int flags, MediaRouter router) {
27909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.cb = cb;
27919a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.type = type;
279266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            this.flags = flags;
2793b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            this.router = router;
27949a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
279566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown
279666f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        public boolean filterRouteEvent(RouteInfo route) {
2797af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown            return filterRouteEvent(route.mSupportedTypes);
2798af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown        }
2799af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown
2800af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown        public boolean filterRouteEvent(int supportedTypes) {
280166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown            return (flags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
2802af574183c274f51d04487a9c8355e9f34a1150f2Jeff Brown                    || (type & supportedTypes) != 0;
280366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown        }
28049a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
28059a1de308cea2d160778fd977825f10a07b49d738Adam Powell
28069a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
28079a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Interface for receiving events about media routing changes.
28089a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * All methods of this interface will be called from the application's main thread.
280966f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * <p>
281066f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * A Callback will only receive events relevant to routes that the callback
281166f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
281266f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * flag was specified in {@link MediaRouter#addCallback(int, Callback, int)}.
281366f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * </p>
28149a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
281566f3b39ec452a8a03e3254e77e19ccb764ee931fJeff Brown     * @see MediaRouter#addCallback(int, Callback, int)
28169a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#removeCallback(Callback)
28179a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
28180d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static abstract class Callback {
28199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
28209a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes selected as the active route
28219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
28229a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
2823d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
28249a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been selected
28259a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been selected for the given route types
28269a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
28270d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
28289a1de308cea2d160778fd977825f10a07b49d738Adam Powell
28299a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
28309a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes unselected as the active route
28319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
28329a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
2833d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
28349a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been unselected
28359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been unselected for the given route types
28369a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
28370d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
28389a1de308cea2d160778fd977825f10a07b49d738Adam Powell
28399a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
28409a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was added.
28419a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
2842d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
28439a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has become available for use
28449a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
28450d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
28469a1de308cea2d160778fd977825f10a07b49d738Adam Powell
28479a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
28489a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was removed.
28499a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
2850d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
28519a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been removed from availability
28529a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
28530d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
28549a1de308cea2d160778fd977825f10a07b49d738Adam Powell
28559a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
28569a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when an aspect of the indicated route has changed.
28579a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
28589a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>This will not indicate that the types supported by this route have
28599a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * changed, only that cosmetic info such as name or status have been updated.</p>
28609a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
2861d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
28629a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info The route that was changed
28639a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
28640d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
2865d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
2866d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
2867d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is added to a group.
2868d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
2869d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
2870d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was added
2871d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was added to
2872d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index The route index within group that info was added at
2873d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
28740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
28750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                int index);
2876d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
2877d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
2878d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is removed from a group.
2879d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
2880d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
2881d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was removed
2882d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was removed from
2883d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
28840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
28858e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
28868e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
28878e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Called when a route's volume changes.
28888e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
28898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param router the MediaRouter reporting the event
28908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param info The route with altered volume
28918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
28928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
289392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown
289492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        /**
289592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * Called when a route's presentation display changes.
289692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * <p>
289792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * This method is called whenever the route's presentation display becomes
289892130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * available, is removes or has changes to some of its properties (such as its size).
289992130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * </p>
290092130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
290192130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @param router the MediaRouter reporting the event
290292130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @param info The route whose presentation display changed
290392130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         *
290492130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         * @see RouteInfo#getPresentationDisplay()
290592130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown         */
290692130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
290792130f6407dc51c58b3b941d28a6daf4e04b8d62Jeff Brown        }
29089a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
29099a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29109a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
29110d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Stub implementation of {@link MediaRouter.Callback}.
29120d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Each abstract method is defined as a no-op. Override just the ones
29139a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * you need.
29149a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
29150d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static class SimpleCallback extends Callback {
29169a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29179a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2918d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
29199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
29209a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29219a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2922d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
29239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
29249a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2926d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteAdded(MediaRouter router, RouteInfo info) {
29279a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
29289a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29299a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2930d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
29319a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
29329a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29339a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2934d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteChanged(MediaRouter router, RouteInfo info) {
29359a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
29369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2938d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
2939d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                int index) {
29409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
29419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
29429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
2943d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
29449a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2945d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
29468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
29478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
29488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
29499a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
29501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
29511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    static class VolumeCallbackInfo {
29521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final VolumeCallback vcb;
29531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final RouteInfo route;
29541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
29551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public VolumeCallbackInfo(VolumeCallback vcb, RouteInfo route) {
29561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.vcb = vcb;
29571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.route = route;
29581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
29591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
29601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
29611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    /**
29621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * Interface for receiving events about volume changes.
29631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * All methods of this interface will be called from the application's main thread.
29641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
29651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * <p>A VolumeCallback will only receive events relevant to routes that the callback
29661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * was registered for.</p>
29671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
29681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * @see UserRouteInfo#setVolumeCallback(VolumeCallback)
29691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     */
29701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    public static abstract class VolumeCallback {
29711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
29721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be increased or decreased.
29731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
29741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param direction an integer indicating whether the volume is to be increased
29751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     (positive value) or decreased (negative value).
29761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     For bundled changes, the absolute value indicates the number of changes
29771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     in the same direction, e.g. +3 corresponds to three "volume up" changes.
29781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
29791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeUpdateRequest(RouteInfo info, int direction);
29801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
29811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be set to the given value
29821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
29831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume an integer indicating the new volume value that should be used, always
29841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     between 0 and the value set by {@link UserRouteInfo#setVolumeMax(int)}.
29851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
29861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeSetRequest(RouteInfo info, int volume);
29871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
29881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
29898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static class VolumeChangeReceiver extends BroadcastReceiver {
29908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
29918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onReceive(Context context, Intent intent) {
29928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
29938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
29948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        -1);
29958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (streamType != AudioManager.STREAM_MUSIC) {
29968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
29978e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
29988e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
29998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
30008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int oldVolume = intent.getIntExtra(
30018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
30028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (newVolume != oldVolume) {
30038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    systemVolumeChanged(newVolume);
30048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
30058e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
30068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
3007705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    }
30088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
3009705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell    static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
3010705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        @Override
3011705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        public void onReceive(Context context, Intent intent) {
3012705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
3013705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
3014705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell                        DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
3015705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell            }
3016705ab808cf023e0cc38c2ba7cdb9571942cdc04fAdam Powell        }
30178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
30189a1de308cea2d160778fd977825f10a07b49d738Adam Powell}
3019