Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 import com.android.internal.util.Objects;
     20 
     21 import android.Manifest;
     22 import android.app.ActivityThread;
     23 import android.content.BroadcastReceiver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.content.pm.PackageManager;
     28 import android.content.res.Resources;
     29 import android.graphics.drawable.Drawable;
     30 import android.hardware.display.DisplayManager;
     31 import android.hardware.display.WifiDisplay;
     32 import android.hardware.display.WifiDisplayStatus;
     33 import android.os.Handler;
     34 import android.os.IBinder;
     35 import android.os.Process;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.os.UserHandle;
     39 import android.text.TextUtils;
     40 import android.util.Log;
     41 import android.view.Display;
     42 
     43 import java.util.ArrayList;
     44 import java.util.HashMap;
     45 import java.util.List;
     46 import java.util.concurrent.CopyOnWriteArrayList;
     47 
     48 /**
     49  * MediaRouter allows applications to control the routing of media channels
     50  * and streams from the current device to external speakers and destination devices.
     51  *
     52  * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
     53  * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
     54  * Context.MEDIA_ROUTER_SERVICE}.
     55  *
     56  * <p>The media router API is not thread-safe; all interactions with it must be
     57  * done from the main thread of the process.</p>
     58  */
     59 public class MediaRouter {
     60     private static final String TAG = "MediaRouter";
     61     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     62 
     63     static class Static implements DisplayManager.DisplayListener {
     64         final Context mAppContext;
     65         final Resources mResources;
     66         final IAudioService mAudioService;
     67         final DisplayManager mDisplayService;
     68         final IMediaRouterService mMediaRouterService;
     69         final Handler mHandler;
     70         final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
     71                 new CopyOnWriteArrayList<CallbackInfo>();
     72 
     73         final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
     74         final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
     75 
     76         final RouteCategory mSystemCategory;
     77 
     78         final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
     79 
     80         RouteInfo mDefaultAudioVideo;
     81         RouteInfo mBluetoothA2dpRoute;
     82 
     83         RouteInfo mSelectedRoute;
     84 
     85         final boolean mCanConfigureWifiDisplays;
     86         boolean mActivelyScanningWifiDisplays;
     87         String mPreviousActiveWifiDisplayAddress;
     88 
     89         int mDiscoveryRequestRouteTypes;
     90         boolean mDiscoverRequestActiveScan;
     91 
     92         int mCurrentUserId = -1;
     93         IMediaRouterClient mClient;
     94         MediaRouterClientState mClientState;
     95 
     96         final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
     97             @Override
     98             public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
     99                 mHandler.post(new Runnable() {
    100                     @Override public void run() {
    101                         updateAudioRoutes(newRoutes);
    102                     }
    103                 });
    104             }
    105         };
    106 
    107         Static(Context appContext) {
    108             mAppContext = appContext;
    109             mResources = Resources.getSystem();
    110             mHandler = new Handler(appContext.getMainLooper());
    111 
    112             IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
    113             mAudioService = IAudioService.Stub.asInterface(b);
    114 
    115             mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
    116 
    117             mMediaRouterService = IMediaRouterService.Stub.asInterface(
    118                     ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
    119 
    120             mSystemCategory = new RouteCategory(
    121                     com.android.internal.R.string.default_audio_route_category_name,
    122                     ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
    123             mSystemCategory.mIsSystem = true;
    124 
    125             // Only the system can configure wifi displays.  The display manager
    126             // enforces this with a permission check.  Set a flag here so that we
    127             // know whether this process is actually allowed to scan and connect.
    128             mCanConfigureWifiDisplays = appContext.checkPermission(
    129                     Manifest.permission.CONFIGURE_WIFI_DISPLAY,
    130                     Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
    131         }
    132 
    133         // Called after sStatic is initialized
    134         void startMonitoringRoutes(Context appContext) {
    135             mDefaultAudioVideo = new RouteInfo(mSystemCategory);
    136             mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
    137             mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
    138             mDefaultAudioVideo.updatePresentationDisplay();
    139             addRouteStatic(mDefaultAudioVideo);
    140 
    141             // This will select the active wifi display route if there is one.
    142             updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
    143 
    144             appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
    145                     new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
    146             appContext.registerReceiver(new VolumeChangeReceiver(),
    147                     new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
    148 
    149             mDisplayService.registerDisplayListener(this, mHandler);
    150 
    151             AudioRoutesInfo newAudioRoutes = null;
    152             try {
    153                 newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
    154             } catch (RemoteException e) {
    155             }
    156             if (newAudioRoutes != null) {
    157                 // This will select the active BT route if there is one and the current
    158                 // selected route is the default system route, or if there is no selected
    159                 // route yet.
    160                 updateAudioRoutes(newAudioRoutes);
    161             }
    162 
    163             // Bind to the media router service.
    164             rebindAsUser(UserHandle.myUserId());
    165 
    166             // Select the default route if the above didn't sync us up
    167             // appropriately with relevant system state.
    168             if (mSelectedRoute == null) {
    169                 selectDefaultRouteStatic();
    170             }
    171         }
    172 
    173         void updateAudioRoutes(AudioRoutesInfo newRoutes) {
    174             if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
    175                 mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
    176                 int name;
    177                 if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
    178                         || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
    179                     name = com.android.internal.R.string.default_audio_route_name_headphones;
    180                 } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
    181                     name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
    182                 } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
    183                     name = com.android.internal.R.string.default_media_route_name_hdmi;
    184                 } else {
    185                     name = com.android.internal.R.string.default_audio_route_name;
    186                 }
    187                 sStatic.mDefaultAudioVideo.mNameResId = name;
    188                 dispatchRouteChanged(sStatic.mDefaultAudioVideo);
    189             }
    190 
    191             final int mainType = mCurAudioRoutesInfo.mMainType;
    192 
    193             boolean a2dpEnabled;
    194             try {
    195                 a2dpEnabled = mAudioService.isBluetoothA2dpOn();
    196             } catch (RemoteException e) {
    197                 Log.e(TAG, "Error querying Bluetooth A2DP state", e);
    198                 a2dpEnabled = false;
    199             }
    200 
    201             if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
    202                 mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
    203                 if (mCurAudioRoutesInfo.mBluetoothName != null) {
    204                     if (sStatic.mBluetoothA2dpRoute == null) {
    205                         final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
    206                         info.mName = mCurAudioRoutesInfo.mBluetoothName;
    207                         info.mDescription = sStatic.mResources.getText(
    208                                 com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
    209                         info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
    210                         sStatic.mBluetoothA2dpRoute = info;
    211                         addRouteStatic(sStatic.mBluetoothA2dpRoute);
    212                     } else {
    213                         sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
    214                         dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
    215                     }
    216                 } else if (sStatic.mBluetoothA2dpRoute != null) {
    217                     removeRouteStatic(sStatic.mBluetoothA2dpRoute);
    218                     sStatic.mBluetoothA2dpRoute = null;
    219                 }
    220             }
    221 
    222             if (mBluetoothA2dpRoute != null) {
    223                 if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
    224                         mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
    225                     selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
    226                 } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
    227                         a2dpEnabled) {
    228                     selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
    229                 }
    230             }
    231         }
    232 
    233         void updateDiscoveryRequest() {
    234             // What are we looking for today?
    235             int routeTypes = 0;
    236             int passiveRouteTypes = 0;
    237             boolean activeScan = false;
    238             boolean activeScanWifiDisplay = false;
    239             final int count = mCallbacks.size();
    240             for (int i = 0; i < count; i++) {
    241                 CallbackInfo cbi = mCallbacks.get(i);
    242                 if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
    243                         | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {
    244                     // Discovery explicitly requested.
    245                     routeTypes |= cbi.type;
    246                 } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {
    247                     // Discovery only passively requested.
    248                     passiveRouteTypes |= cbi.type;
    249                 } else {
    250                     // Legacy case since applications don't specify the discovery flag.
    251                     // Unfortunately we just have to assume they always need discovery
    252                     // whenever they have a callback registered.
    253                     routeTypes |= cbi.type;
    254                 }
    255                 if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
    256                     activeScan = true;
    257                     if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
    258                         activeScanWifiDisplay = true;
    259                     }
    260                 }
    261             }
    262             if (routeTypes != 0 || activeScan) {
    263                 // If someone else requests discovery then enable the passive listeners.
    264                 // This is used by the MediaRouteButton and MediaRouteActionProvider since
    265                 // they don't receive lifecycle callbacks from the Activity.
    266                 routeTypes |= passiveRouteTypes;
    267             }
    268 
    269             // Update wifi display scanning.
    270             // TODO: All of this should be managed by the media router service.
    271             if (mCanConfigureWifiDisplays) {
    272                 if (mSelectedRoute != null
    273                         && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {
    274                     // Don't scan while already connected to a remote display since
    275                     // it may interfere with the ongoing transmission.
    276                     activeScanWifiDisplay = false;
    277                 }
    278                 if (activeScanWifiDisplay) {
    279                     if (!mActivelyScanningWifiDisplays) {
    280                         mActivelyScanningWifiDisplays = true;
    281                         mDisplayService.startWifiDisplayScan();
    282                     }
    283                 } else {
    284                     if (mActivelyScanningWifiDisplays) {
    285                         mActivelyScanningWifiDisplays = false;
    286                         mDisplayService.stopWifiDisplayScan();
    287                     }
    288                 }
    289             }
    290 
    291             // Tell the media router service all about it.
    292             if (routeTypes != mDiscoveryRequestRouteTypes
    293                     || activeScan != mDiscoverRequestActiveScan) {
    294                 mDiscoveryRequestRouteTypes = routeTypes;
    295                 mDiscoverRequestActiveScan = activeScan;
    296                 publishClientDiscoveryRequest();
    297             }
    298         }
    299 
    300         @Override
    301         public void onDisplayAdded(int displayId) {
    302             updatePresentationDisplays(displayId);
    303         }
    304 
    305         @Override
    306         public void onDisplayChanged(int displayId) {
    307             updatePresentationDisplays(displayId);
    308         }
    309 
    310         @Override
    311         public void onDisplayRemoved(int displayId) {
    312             updatePresentationDisplays(displayId);
    313         }
    314 
    315         public Display[] getAllPresentationDisplays() {
    316             return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
    317         }
    318 
    319         private void updatePresentationDisplays(int changedDisplayId) {
    320             final int count = mRoutes.size();
    321             for (int i = 0; i < count; i++) {
    322                 final RouteInfo route = mRoutes.get(i);
    323                 if (route.updatePresentationDisplay() || (route.mPresentationDisplay != null
    324                         && route.mPresentationDisplay.getDisplayId() == changedDisplayId)) {
    325                     dispatchRoutePresentationDisplayChanged(route);
    326                 }
    327             }
    328         }
    329 
    330         void setSelectedRoute(RouteInfo info, boolean explicit) {
    331             // Must be non-reentrant.
    332             mSelectedRoute = info;
    333             publishClientSelectedRoute(explicit);
    334         }
    335 
    336         void rebindAsUser(int userId) {
    337             if (mCurrentUserId != userId || userId < 0 || mClient == null) {
    338                 if (mClient != null) {
    339                     try {
    340                         mMediaRouterService.unregisterClient(mClient);
    341                     } catch (RemoteException ex) {
    342                         Log.e(TAG, "Unable to unregister media router client.", ex);
    343                     }
    344                     mClient = null;
    345                 }
    346 
    347                 mCurrentUserId = userId;
    348 
    349                 try {
    350                     Client client = new Client();
    351                     mMediaRouterService.registerClientAsUser(client,
    352                             mAppContext.getPackageName(), userId);
    353                     mClient = client;
    354                 } catch (RemoteException ex) {
    355                     Log.e(TAG, "Unable to register media router client.", ex);
    356                 }
    357 
    358                 publishClientDiscoveryRequest();
    359                 publishClientSelectedRoute(false);
    360                 updateClientState();
    361             }
    362         }
    363 
    364         void publishClientDiscoveryRequest() {
    365             if (mClient != null) {
    366                 try {
    367                     mMediaRouterService.setDiscoveryRequest(mClient,
    368                             mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan);
    369                 } catch (RemoteException ex) {
    370                     Log.e(TAG, "Unable to publish media router client discovery request.", ex);
    371                 }
    372             }
    373         }
    374 
    375         void publishClientSelectedRoute(boolean explicit) {
    376             if (mClient != null) {
    377                 try {
    378                     mMediaRouterService.setSelectedRoute(mClient,
    379                             mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null,
    380                             explicit);
    381                 } catch (RemoteException ex) {
    382                     Log.e(TAG, "Unable to publish media router client selected route.", ex);
    383                 }
    384             }
    385         }
    386 
    387         void updateClientState() {
    388             // Update the client state.
    389             mClientState = null;
    390             if (mClient != null) {
    391                 try {
    392                     mClientState = mMediaRouterService.getState(mClient);
    393                 } catch (RemoteException ex) {
    394                     Log.e(TAG, "Unable to retrieve media router client state.", ex);
    395                 }
    396             }
    397             final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
    398                     mClientState != null ? mClientState.routes : null;
    399             final String globallySelectedRouteId = mClientState != null ?
    400                     mClientState.globallySelectedRouteId : null;
    401 
    402             // Add or update routes.
    403             final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0;
    404             for (int i = 0; i < globalRouteCount; i++) {
    405                 final MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(i);
    406                 RouteInfo route = findGlobalRoute(globalRoute.id);
    407                 if (route == null) {
    408                     route = makeGlobalRoute(globalRoute);
    409                     addRouteStatic(route);
    410                 } else {
    411                     updateGlobalRoute(route, globalRoute);
    412                 }
    413             }
    414 
    415             // Synchronize state with the globally selected route.
    416             if (globallySelectedRouteId != null) {
    417                 final RouteInfo route = findGlobalRoute(globallySelectedRouteId);
    418                 if (route == null) {
    419                     Log.w(TAG, "Could not find new globally selected route: "
    420                             + globallySelectedRouteId);
    421                 } else if (route != mSelectedRoute) {
    422                     if (DEBUG) {
    423                         Log.d(TAG, "Selecting new globally selected route: " + route);
    424                     }
    425                     selectRouteStatic(route.mSupportedTypes, route, false);
    426                 }
    427             } else if (mSelectedRoute != null && mSelectedRoute.mGlobalRouteId != null) {
    428                 if (DEBUG) {
    429                     Log.d(TAG, "Unselecting previous globally selected route: " + mSelectedRoute);
    430                 }
    431                 selectDefaultRouteStatic();
    432             }
    433 
    434             // Remove defunct routes.
    435             outer: for (int i = mRoutes.size(); i-- > 0; ) {
    436                 final RouteInfo route = mRoutes.get(i);
    437                 final String globalRouteId = route.mGlobalRouteId;
    438                 if (globalRouteId != null) {
    439                     for (int j = 0; j < globalRouteCount; j++) {
    440                         MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(j);
    441                         if (globalRouteId.equals(globalRoute.id)) {
    442                             continue outer; // found
    443                         }
    444                     }
    445                     // not found
    446                     removeRouteStatic(route);
    447                 }
    448             }
    449         }
    450 
    451         void requestSetVolume(RouteInfo route, int volume) {
    452             if (route.mGlobalRouteId != null && mClient != null) {
    453                 try {
    454                     mMediaRouterService.requestSetVolume(mClient,
    455                             route.mGlobalRouteId, volume);
    456                 } catch (RemoteException ex) {
    457                     Log.w(TAG, "Unable to request volume change.", ex);
    458                 }
    459             }
    460         }
    461 
    462         void requestUpdateVolume(RouteInfo route, int direction) {
    463             if (route.mGlobalRouteId != null && mClient != null) {
    464                 try {
    465                     mMediaRouterService.requestUpdateVolume(mClient,
    466                             route.mGlobalRouteId, direction);
    467                 } catch (RemoteException ex) {
    468                     Log.w(TAG, "Unable to request volume change.", ex);
    469                 }
    470             }
    471         }
    472 
    473         RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
    474             RouteInfo route = new RouteInfo(sStatic.mSystemCategory);
    475             route.mGlobalRouteId = globalRoute.id;
    476             route.mName = globalRoute.name;
    477             route.mDescription = globalRoute.description;
    478             route.mSupportedTypes = globalRoute.supportedTypes;
    479             route.mEnabled = globalRoute.enabled;
    480             route.setRealStatusCode(globalRoute.statusCode);
    481             route.mPlaybackType = globalRoute.playbackType;
    482             route.mPlaybackStream = globalRoute.playbackStream;
    483             route.mVolume = globalRoute.volume;
    484             route.mVolumeMax = globalRoute.volumeMax;
    485             route.mVolumeHandling = globalRoute.volumeHandling;
    486             route.mPresentationDisplayId = globalRoute.presentationDisplayId;
    487             route.updatePresentationDisplay();
    488             return route;
    489         }
    490 
    491         void updateGlobalRoute(RouteInfo route, MediaRouterClientState.RouteInfo globalRoute) {
    492             boolean changed = false;
    493             boolean volumeChanged = false;
    494             boolean presentationDisplayChanged = false;
    495 
    496             if (!Objects.equal(route.mName, globalRoute.name)) {
    497                 route.mName = globalRoute.name;
    498                 changed = true;
    499             }
    500             if (!Objects.equal(route.mDescription, globalRoute.description)) {
    501                 route.mDescription = globalRoute.description;
    502                 changed = true;
    503             }
    504             final int oldSupportedTypes = route.mSupportedTypes;
    505             if (oldSupportedTypes != globalRoute.supportedTypes) {
    506                 route.mSupportedTypes = globalRoute.supportedTypes;
    507                 changed = true;
    508             }
    509             if (route.mEnabled != globalRoute.enabled) {
    510                 route.mEnabled = globalRoute.enabled;
    511                 changed = true;
    512             }
    513             if (route.mRealStatusCode != globalRoute.statusCode) {
    514                 route.setRealStatusCode(globalRoute.statusCode);
    515                 changed = true;
    516             }
    517             if (route.mPlaybackType != globalRoute.playbackType) {
    518                 route.mPlaybackType = globalRoute.playbackType;
    519                 changed = true;
    520             }
    521             if (route.mPlaybackStream != globalRoute.playbackStream) {
    522                 route.mPlaybackStream = globalRoute.playbackStream;
    523                 changed = true;
    524             }
    525             if (route.mVolume != globalRoute.volume) {
    526                 route.mVolume = globalRoute.volume;
    527                 changed = true;
    528                 volumeChanged = true;
    529             }
    530             if (route.mVolumeMax != globalRoute.volumeMax) {
    531                 route.mVolumeMax = globalRoute.volumeMax;
    532                 changed = true;
    533                 volumeChanged = true;
    534             }
    535             if (route.mVolumeHandling != globalRoute.volumeHandling) {
    536                 route.mVolumeHandling = globalRoute.volumeHandling;
    537                 changed = true;
    538                 volumeChanged = true;
    539             }
    540             if (route.mPresentationDisplayId != globalRoute.presentationDisplayId) {
    541                 route.mPresentationDisplayId = globalRoute.presentationDisplayId;
    542                 route.updatePresentationDisplay();
    543                 changed = true;
    544                 presentationDisplayChanged = true;
    545             }
    546 
    547             if (changed) {
    548                 dispatchRouteChanged(route, oldSupportedTypes);
    549             }
    550             if (volumeChanged) {
    551                 dispatchRouteVolumeChanged(route);
    552             }
    553             if (presentationDisplayChanged) {
    554                 dispatchRoutePresentationDisplayChanged(route);
    555             }
    556         }
    557 
    558         RouteInfo findGlobalRoute(String globalRouteId) {
    559             final int count = mRoutes.size();
    560             for (int i = 0; i < count; i++) {
    561                 final RouteInfo route = mRoutes.get(i);
    562                 if (globalRouteId.equals(route.mGlobalRouteId)) {
    563                     return route;
    564                 }
    565             }
    566             return null;
    567         }
    568 
    569         final class Client extends IMediaRouterClient.Stub {
    570             @Override
    571             public void onStateChanged() {
    572                 mHandler.post(new Runnable() {
    573                     @Override
    574                     public void run() {
    575                         if (Client.this == mClient) {
    576                             updateClientState();
    577                         }
    578                     }
    579                 });
    580             }
    581         }
    582     }
    583 
    584     static Static sStatic;
    585 
    586     /**
    587      * Route type flag for live audio.
    588      *
    589      * <p>A device that supports live audio routing will allow the media audio stream
    590      * to be routed to supported destinations. This can include internal speakers or
    591      * audio jacks on the device itself, A2DP devices, and more.</p>
    592      *
    593      * <p>Once initiated this routing is transparent to the application. All audio
    594      * played on the media stream will be routed to the selected destination.</p>
    595      */
    596     public static final int ROUTE_TYPE_LIVE_AUDIO = 1 << 0;
    597 
    598     /**
    599      * Route type flag for live video.
    600      *
    601      * <p>A device that supports live video routing will allow a mirrored version
    602      * of the device's primary display or a customized
    603      * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
    604      *
    605      * <p>Once initiated, display mirroring is transparent to the application.
    606      * While remote routing is active the application may use a
    607      * {@link android.app.Presentation Presentation} to replace the mirrored view
    608      * on the external display with different content.</p>
    609      *
    610      * @see RouteInfo#getPresentationDisplay()
    611      * @see android.app.Presentation
    612      */
    613     public static final int ROUTE_TYPE_LIVE_VIDEO = 1 << 1;
    614 
    615     /**
    616      * Temporary interop constant to identify remote displays.
    617      * @hide To be removed when media router API is updated.
    618      */
    619     public static final int ROUTE_TYPE_REMOTE_DISPLAY = 1 << 2;
    620 
    621     /**
    622      * Route type flag for application-specific usage.
    623      *
    624      * <p>Unlike other media route types, user routes are managed by the application.
    625      * The MediaRouter will manage and dispatch events for user routes, but the application
    626      * is expected to interpret the meaning of these events and perform the requested
    627      * routing tasks.</p>
    628      */
    629     public static final int ROUTE_TYPE_USER = 1 << 23;
    630 
    631     static final int ROUTE_TYPE_ANY = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
    632             | ROUTE_TYPE_REMOTE_DISPLAY | ROUTE_TYPE_USER;
    633 
    634     /**
    635      * Flag for {@link #addCallback}: Actively scan for routes while this callback
    636      * is registered.
    637      * <p>
    638      * When this flag is specified, the media router will actively scan for new
    639      * routes.  Certain routes, such as wifi display routes, may not be discoverable
    640      * except when actively scanning.  This flag is typically used when the route picker
    641      * dialog has been opened by the user to ensure that the route information is
    642      * up to date.
    643      * </p><p>
    644      * Active scanning may consume a significant amount of power and may have intrusive
    645      * effects on wireless connectivity.  Therefore it is important that active scanning
    646      * only be requested when it is actually needed to satisfy a user request to
    647      * discover and select a new route.
    648      * </p>
    649      */
    650     public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
    651 
    652     /**
    653      * Flag for {@link #addCallback}: Do not filter route events.
    654      * <p>
    655      * When this flag is specified, the callback will be invoked for event that affect any
    656      * route even if they do not match the callback's filter.
    657      * </p>
    658      */
    659     public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
    660 
    661     /**
    662      * Explicitly requests discovery.
    663      *
    664      * @hide Future API ported from support library.  Revisit this later.
    665      */
    666     public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
    667 
    668     /**
    669      * Requests that discovery be performed but only if there is some other active
    670      * callback already registered.
    671      *
    672      * @hide Compatibility workaround for the fact that applications do not currently
    673      * request discovery explicitly (except when using the support library API).
    674      */
    675     public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3;
    676 
    677     /**
    678      * Flag for {@link #isRouteAvailable}: Ignore the default route.
    679      * <p>
    680      * This flag is used to determine whether a matching non-default route is available.
    681      * This constraint may be used to decide whether to offer the route chooser dialog
    682      * to the user.  There is no point offering the chooser if there are no
    683      * non-default choices.
    684      * </p>
    685      *
    686      * @hide Future API ported from support library.  Revisit this later.
    687      */
    688     public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
    689 
    690     // Maps application contexts
    691     static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
    692 
    693     static String typesToString(int types) {
    694         final StringBuilder result = new StringBuilder();
    695         if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
    696             result.append("ROUTE_TYPE_LIVE_AUDIO ");
    697         }
    698         if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
    699             result.append("ROUTE_TYPE_LIVE_VIDEO ");
    700         }
    701         if ((types & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
    702             result.append("ROUTE_TYPE_REMOTE_DISPLAY ");
    703         }
    704         if ((types & ROUTE_TYPE_USER) != 0) {
    705             result.append("ROUTE_TYPE_USER ");
    706         }
    707         return result.toString();
    708     }
    709 
    710     /** @hide */
    711     public MediaRouter(Context context) {
    712         synchronized (Static.class) {
    713             if (sStatic == null) {
    714                 final Context appContext = context.getApplicationContext();
    715                 sStatic = new Static(appContext);
    716                 sStatic.startMonitoringRoutes(appContext);
    717             }
    718         }
    719     }
    720 
    721     /**
    722      * Gets the default route for playing media content on the system.
    723      * <p>
    724      * The system always provides a default route.
    725      * </p>
    726      *
    727      * @return The default route, which is guaranteed to never be null.
    728      */
    729     public RouteInfo getDefaultRoute() {
    730         return sStatic.mDefaultAudioVideo;
    731     }
    732 
    733     /**
    734      * @hide for use by framework routing UI
    735      */
    736     public RouteCategory getSystemCategory() {
    737         return sStatic.mSystemCategory;
    738     }
    739 
    740     /** @hide */
    741     public RouteInfo getSelectedRoute() {
    742         return getSelectedRoute(ROUTE_TYPE_ANY);
    743     }
    744 
    745     /**
    746      * Return the currently selected route for any of the given types
    747      *
    748      * @param type route types
    749      * @return the selected route
    750      */
    751     public RouteInfo getSelectedRoute(int type) {
    752         if (sStatic.mSelectedRoute != null &&
    753                 (sStatic.mSelectedRoute.mSupportedTypes & type) != 0) {
    754             // If the selected route supports any of the types supplied, it's still considered
    755             // 'selected' for that type.
    756             return sStatic.mSelectedRoute;
    757         } else if (type == ROUTE_TYPE_USER) {
    758             // The caller specifically asked for a user route and the currently selected route
    759             // doesn't qualify.
    760             return null;
    761         }
    762         // If the above didn't match and we're not specifically asking for a user route,
    763         // consider the default selected.
    764         return sStatic.mDefaultAudioVideo;
    765     }
    766 
    767     /**
    768      * Returns true if there is a route that matches the specified types.
    769      * <p>
    770      * This method returns true if there are any available routes that match the types
    771      * regardless of whether they are enabled or disabled.  If the
    772      * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
    773      * the method will only consider non-default routes.
    774      * </p>
    775      *
    776      * @param types The types to match.
    777      * @param flags Flags to control the determination of whether a route may be available.
    778      * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
    779      * @return True if a matching route may be available.
    780      *
    781      * @hide Future API ported from support library.  Revisit this later.
    782      */
    783     public boolean isRouteAvailable(int types, int flags) {
    784         final int count = sStatic.mRoutes.size();
    785         for (int i = 0; i < count; i++) {
    786             RouteInfo route = sStatic.mRoutes.get(i);
    787             if (route.matchesTypes(types)) {
    788                 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0
    789                         || route != sStatic.mDefaultAudioVideo) {
    790                     return true;
    791                 }
    792             }
    793         }
    794 
    795         // It doesn't look like we can find a matching route right now.
    796         return false;
    797     }
    798 
    799     /**
    800      * Add a callback to listen to events about specific kinds of media routes.
    801      * If the specified callback is already registered, its registration will be updated for any
    802      * additional route types specified.
    803      * <p>
    804      * This is a convenience method that has the same effect as calling
    805      * {@link #addCallback(int, Callback, int)} without flags.
    806      * </p>
    807      *
    808      * @param types Types of routes this callback is interested in
    809      * @param cb Callback to add
    810      */
    811     public void addCallback(int types, Callback cb) {
    812         addCallback(types, cb, 0);
    813     }
    814 
    815     /**
    816      * Add a callback to listen to events about specific kinds of media routes.
    817      * If the specified callback is already registered, its registration will be updated for any
    818      * additional route types specified.
    819      * <p>
    820      * By default, the callback will only be invoked for events that affect routes
    821      * that match the specified selector.  The filtering may be disabled by specifying
    822      * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag.
    823      * </p>
    824      *
    825      * @param types Types of routes this callback is interested in
    826      * @param cb Callback to add
    827      * @param flags Flags to control the behavior of the callback.
    828      * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
    829      * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
    830      */
    831     public void addCallback(int types, Callback cb, int flags) {
    832         CallbackInfo info;
    833         int index = findCallbackInfo(cb);
    834         if (index >= 0) {
    835             info = sStatic.mCallbacks.get(index);
    836             info.type |= types;
    837             info.flags |= flags;
    838         } else {
    839             info = new CallbackInfo(cb, types, flags, this);
    840             sStatic.mCallbacks.add(info);
    841         }
    842         sStatic.updateDiscoveryRequest();
    843     }
    844 
    845     /**
    846      * Remove the specified callback. It will no longer receive events about media routing.
    847      *
    848      * @param cb Callback to remove
    849      */
    850     public void removeCallback(Callback cb) {
    851         int index = findCallbackInfo(cb);
    852         if (index >= 0) {
    853             sStatic.mCallbacks.remove(index);
    854             sStatic.updateDiscoveryRequest();
    855         } else {
    856             Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
    857         }
    858     }
    859 
    860     private int findCallbackInfo(Callback cb) {
    861         final int count = sStatic.mCallbacks.size();
    862         for (int i = 0; i < count; i++) {
    863             final CallbackInfo info = sStatic.mCallbacks.get(i);
    864             if (info.cb == cb) {
    865                 return i;
    866             }
    867         }
    868         return -1;
    869     }
    870 
    871     /**
    872      * Select the specified route to use for output of the given media types.
    873      * <p class="note">
    874      * As API version 18, this function may be used to select any route.
    875      * In prior versions, this function could only be used to select user
    876      * routes and would ignore any attempt to select a system route.
    877      * </p>
    878      *
    879      * @param types type flags indicating which types this route should be used for.
    880      *              The route must support at least a subset.
    881      * @param route Route to select
    882      */
    883     public void selectRoute(int types, RouteInfo route) {
    884         selectRouteStatic(types, route, true);
    885     }
    886 
    887     /**
    888      * @hide internal use
    889      */
    890     public void selectRouteInt(int types, RouteInfo route, boolean explicit) {
    891         selectRouteStatic(types, route, explicit);
    892     }
    893 
    894     static void selectRouteStatic(int types, RouteInfo route, boolean explicit) {
    895         final RouteInfo oldRoute = sStatic.mSelectedRoute;
    896         if (oldRoute == route) return;
    897         if (!route.matchesTypes(types)) {
    898             Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
    899                     typesToString(route.getSupportedTypes()) + " into route types " +
    900                     typesToString(types));
    901             return;
    902         }
    903 
    904         final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
    905         if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
    906                 (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
    907             try {
    908                 sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
    909             } catch (RemoteException e) {
    910                 Log.e(TAG, "Error changing Bluetooth A2DP state", e);
    911             }
    912         }
    913 
    914         final WifiDisplay activeDisplay =
    915                 sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
    916         final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
    917         final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
    918         if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
    919             if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
    920                 if (sStatic.mCanConfigureWifiDisplays) {
    921                     sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
    922                 } else {
    923                     Log.e(TAG, "Cannot connect to wifi displays because this process "
    924                             + "is not allowed to do so.");
    925                 }
    926             } else if (activeDisplay != null && !newRouteHasAddress) {
    927                 sStatic.mDisplayService.disconnectWifiDisplay();
    928             }
    929         }
    930 
    931         sStatic.setSelectedRoute(route, explicit);
    932 
    933         if (oldRoute != null) {
    934             dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
    935             if (oldRoute.resolveStatusCode()) {
    936                 dispatchRouteChanged(oldRoute);
    937             }
    938         }
    939         if (route != null) {
    940             if (route.resolveStatusCode()) {
    941                 dispatchRouteChanged(route);
    942             }
    943             dispatchRouteSelected(types & route.getSupportedTypes(), route);
    944         }
    945 
    946         // The behavior of active scans may depend on the currently selected route.
    947         sStatic.updateDiscoveryRequest();
    948     }
    949 
    950     static void selectDefaultRouteStatic() {
    951         // TODO: Be smarter about the route types here; this selects for all valid.
    952         if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
    953                 && sStatic.mBluetoothA2dpRoute != null) {
    954             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
    955         } else {
    956             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
    957         }
    958     }
    959 
    960     /**
    961      * Compare the device address of a display and a route.
    962      * Nulls/no device address will match another null/no address.
    963      */
    964     static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
    965         final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
    966         if (display == null && !routeHasAddress) {
    967             return true;
    968         }
    969 
    970         if (display != null && routeHasAddress) {
    971             return display.getDeviceAddress().equals(info.mDeviceAddress);
    972         }
    973         return false;
    974     }
    975 
    976     /**
    977      * Add an app-specified route for media to the MediaRouter.
    978      * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
    979      *
    980      * @param info Definition of the route to add
    981      * @see #createUserRoute(RouteCategory)
    982      * @see #removeUserRoute(UserRouteInfo)
    983      */
    984     public void addUserRoute(UserRouteInfo info) {
    985         addRouteStatic(info);
    986     }
    987 
    988     /**
    989      * @hide Framework use only
    990      */
    991     public void addRouteInt(RouteInfo info) {
    992         addRouteStatic(info);
    993     }
    994 
    995     static void addRouteStatic(RouteInfo info) {
    996         final RouteCategory cat = info.getCategory();
    997         if (!sStatic.mCategories.contains(cat)) {
    998             sStatic.mCategories.add(cat);
    999         }
   1000         if (cat.isGroupable() && !(info instanceof RouteGroup)) {
   1001             // Enforce that any added route in a groupable category must be in a group.
   1002             final RouteGroup group = new RouteGroup(info.getCategory());
   1003             group.mSupportedTypes = info.mSupportedTypes;
   1004             sStatic.mRoutes.add(group);
   1005             dispatchRouteAdded(group);
   1006             group.addRoute(info);
   1007 
   1008             info = group;
   1009         } else {
   1010             sStatic.mRoutes.add(info);
   1011             dispatchRouteAdded(info);
   1012         }
   1013     }
   1014 
   1015     /**
   1016      * Remove an app-specified route for media from the MediaRouter.
   1017      *
   1018      * @param info Definition of the route to remove
   1019      * @see #addUserRoute(UserRouteInfo)
   1020      */
   1021     public void removeUserRoute(UserRouteInfo info) {
   1022         removeRouteStatic(info);
   1023     }
   1024 
   1025     /**
   1026      * Remove all app-specified routes from the MediaRouter.
   1027      *
   1028      * @see #removeUserRoute(UserRouteInfo)
   1029      */
   1030     public void clearUserRoutes() {
   1031         for (int i = 0; i < sStatic.mRoutes.size(); i++) {
   1032             final RouteInfo info = sStatic.mRoutes.get(i);
   1033             // TODO Right now, RouteGroups only ever contain user routes.
   1034             // The code below will need to change if this assumption does.
   1035             if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
   1036                 removeRouteStatic(info);
   1037                 i--;
   1038             }
   1039         }
   1040     }
   1041 
   1042     /**
   1043      * @hide internal use only
   1044      */
   1045     public void removeRouteInt(RouteInfo info) {
   1046         removeRouteStatic(info);
   1047     }
   1048 
   1049     static void removeRouteStatic(RouteInfo info) {
   1050         if (sStatic.mRoutes.remove(info)) {
   1051             final RouteCategory removingCat = info.getCategory();
   1052             final int count = sStatic.mRoutes.size();
   1053             boolean found = false;
   1054             for (int i = 0; i < count; i++) {
   1055                 final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
   1056                 if (removingCat == cat) {
   1057                     found = true;
   1058                     break;
   1059                 }
   1060             }
   1061             if (info.isSelected()) {
   1062                 // Removing the currently selected route? Select the default before we remove it.
   1063                 selectDefaultRouteStatic();
   1064             }
   1065             if (!found) {
   1066                 sStatic.mCategories.remove(removingCat);
   1067             }
   1068             dispatchRouteRemoved(info);
   1069         }
   1070     }
   1071 
   1072     /**
   1073      * Return the number of {@link MediaRouter.RouteCategory categories} currently
   1074      * represented by routes known to this MediaRouter.
   1075      *
   1076      * @return the number of unique categories represented by this MediaRouter's known routes
   1077      */
   1078     public int getCategoryCount() {
   1079         return sStatic.mCategories.size();
   1080     }
   1081 
   1082     /**
   1083      * Return the {@link MediaRouter.RouteCategory category} at the given index.
   1084      * Valid indices are in the range [0-getCategoryCount).
   1085      *
   1086      * @param index which category to return
   1087      * @return the category at index
   1088      */
   1089     public RouteCategory getCategoryAt(int index) {
   1090         return sStatic.mCategories.get(index);
   1091     }
   1092 
   1093     /**
   1094      * Return the number of {@link MediaRouter.RouteInfo routes} currently known
   1095      * to this MediaRouter.
   1096      *
   1097      * @return the number of routes tracked by this router
   1098      */
   1099     public int getRouteCount() {
   1100         return sStatic.mRoutes.size();
   1101     }
   1102 
   1103     /**
   1104      * Return the route at the specified index.
   1105      *
   1106      * @param index index of the route to return
   1107      * @return the route at index
   1108      */
   1109     public RouteInfo getRouteAt(int index) {
   1110         return sStatic.mRoutes.get(index);
   1111     }
   1112 
   1113     static int getRouteCountStatic() {
   1114         return sStatic.mRoutes.size();
   1115     }
   1116 
   1117     static RouteInfo getRouteAtStatic(int index) {
   1118         return sStatic.mRoutes.get(index);
   1119     }
   1120 
   1121     /**
   1122      * Create a new user route that may be modified and registered for use by the application.
   1123      *
   1124      * @param category The category the new route will belong to
   1125      * @return A new UserRouteInfo for use by the application
   1126      *
   1127      * @see #addUserRoute(UserRouteInfo)
   1128      * @see #removeUserRoute(UserRouteInfo)
   1129      * @see #createRouteCategory(CharSequence, boolean)
   1130      */
   1131     public UserRouteInfo createUserRoute(RouteCategory category) {
   1132         return new UserRouteInfo(category);
   1133     }
   1134 
   1135     /**
   1136      * Create a new route category. Each route must belong to a category.
   1137      *
   1138      * @param name Name of the new category
   1139      * @param isGroupable true if routes in this category may be grouped with one another
   1140      * @return the new RouteCategory
   1141      */
   1142     public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
   1143         return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
   1144     }
   1145 
   1146     /**
   1147      * Create a new route category. Each route must belong to a category.
   1148      *
   1149      * @param nameResId Resource ID of the name of the new category
   1150      * @param isGroupable true if routes in this category may be grouped with one another
   1151      * @return the new RouteCategory
   1152      */
   1153     public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
   1154         return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
   1155     }
   1156 
   1157     /**
   1158      * Rebinds the media router to handle routes that belong to the specified user.
   1159      * Requires the interact across users permission to access the routes of another user.
   1160      * <p>
   1161      * This method is a complete hack to work around the singleton nature of the
   1162      * media router when running inside of singleton processes like QuickSettings.
   1163      * This mechanism should be burned to the ground when MediaRouter is redesigned.
   1164      * Ideally the current user would be pulled from the Context but we need to break
   1165      * down MediaRouter.Static before we can get there.
   1166      * </p>
   1167      *
   1168      * @hide
   1169      */
   1170     public void rebindAsUser(int userId) {
   1171         sStatic.rebindAsUser(userId);
   1172     }
   1173 
   1174     static void updateRoute(final RouteInfo info) {
   1175         dispatchRouteChanged(info);
   1176     }
   1177 
   1178     static void dispatchRouteSelected(int type, RouteInfo info) {
   1179         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1180             if (cbi.filterRouteEvent(info)) {
   1181                 cbi.cb.onRouteSelected(cbi.router, type, info);
   1182             }
   1183         }
   1184     }
   1185 
   1186     static void dispatchRouteUnselected(int type, RouteInfo info) {
   1187         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1188             if (cbi.filterRouteEvent(info)) {
   1189                 cbi.cb.onRouteUnselected(cbi.router, type, info);
   1190             }
   1191         }
   1192     }
   1193 
   1194     static void dispatchRouteChanged(RouteInfo info) {
   1195         dispatchRouteChanged(info, info.mSupportedTypes);
   1196     }
   1197 
   1198     static void dispatchRouteChanged(RouteInfo info, int oldSupportedTypes) {
   1199         final int newSupportedTypes = info.mSupportedTypes;
   1200         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1201             // Reconstruct some of the history for callbacks that may not have observed
   1202             // all of the events needed to correctly interpret the current state.
   1203             // FIXME: This is a strong signal that we should deprecate route type filtering
   1204             // completely in the future because it can lead to inconsistencies in
   1205             // applications.
   1206             final boolean oldVisibility = cbi.filterRouteEvent(oldSupportedTypes);
   1207             final boolean newVisibility = cbi.filterRouteEvent(newSupportedTypes);
   1208             if (!oldVisibility && newVisibility) {
   1209                 cbi.cb.onRouteAdded(cbi.router, info);
   1210                 if (info.isSelected()) {
   1211                     cbi.cb.onRouteSelected(cbi.router, newSupportedTypes, info);
   1212                 }
   1213             }
   1214             if (oldVisibility || newVisibility) {
   1215                 cbi.cb.onRouteChanged(cbi.router, info);
   1216             }
   1217             if (oldVisibility && !newVisibility) {
   1218                 if (info.isSelected()) {
   1219                     cbi.cb.onRouteUnselected(cbi.router, oldSupportedTypes, info);
   1220                 }
   1221                 cbi.cb.onRouteRemoved(cbi.router, info);
   1222             }
   1223         }
   1224     }
   1225 
   1226     static void dispatchRouteAdded(RouteInfo info) {
   1227         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1228             if (cbi.filterRouteEvent(info)) {
   1229                 cbi.cb.onRouteAdded(cbi.router, info);
   1230             }
   1231         }
   1232     }
   1233 
   1234     static void dispatchRouteRemoved(RouteInfo info) {
   1235         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1236             if (cbi.filterRouteEvent(info)) {
   1237                 cbi.cb.onRouteRemoved(cbi.router, info);
   1238             }
   1239         }
   1240     }
   1241 
   1242     static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
   1243         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1244             if (cbi.filterRouteEvent(group)) {
   1245                 cbi.cb.onRouteGrouped(cbi.router, info, group, index);
   1246             }
   1247         }
   1248     }
   1249 
   1250     static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
   1251         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1252             if (cbi.filterRouteEvent(group)) {
   1253                 cbi.cb.onRouteUngrouped(cbi.router, info, group);
   1254             }
   1255         }
   1256     }
   1257 
   1258     static void dispatchRouteVolumeChanged(RouteInfo info) {
   1259         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1260             if (cbi.filterRouteEvent(info)) {
   1261                 cbi.cb.onRouteVolumeChanged(cbi.router, info);
   1262             }
   1263         }
   1264     }
   1265 
   1266     static void dispatchRoutePresentationDisplayChanged(RouteInfo info) {
   1267         for (CallbackInfo cbi : sStatic.mCallbacks) {
   1268             if (cbi.filterRouteEvent(info)) {
   1269                 cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info);
   1270             }
   1271         }
   1272     }
   1273 
   1274     static void systemVolumeChanged(int newValue) {
   1275         final RouteInfo selectedRoute = sStatic.mSelectedRoute;
   1276         if (selectedRoute == null) return;
   1277 
   1278         if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
   1279                 selectedRoute == sStatic.mDefaultAudioVideo) {
   1280             dispatchRouteVolumeChanged(selectedRoute);
   1281         } else if (sStatic.mBluetoothA2dpRoute != null) {
   1282             try {
   1283                 dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
   1284                         sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
   1285             } catch (RemoteException e) {
   1286                 Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
   1287             }
   1288         } else {
   1289             dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
   1290         }
   1291     }
   1292 
   1293     static void updateWifiDisplayStatus(WifiDisplayStatus status) {
   1294         WifiDisplay[] displays;
   1295         WifiDisplay activeDisplay;
   1296         if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
   1297             displays = status.getDisplays();
   1298             activeDisplay = status.getActiveDisplay();
   1299 
   1300             // Only the system is able to connect to wifi display routes.
   1301             // The display manager will enforce this with a permission check but it
   1302             // still publishes information about all available displays.
   1303             // Filter the list down to just the active display.
   1304             if (!sStatic.mCanConfigureWifiDisplays) {
   1305                 if (activeDisplay != null) {
   1306                     displays = new WifiDisplay[] { activeDisplay };
   1307                 } else {
   1308                     displays = WifiDisplay.EMPTY_ARRAY;
   1309                 }
   1310             }
   1311         } else {
   1312             displays = WifiDisplay.EMPTY_ARRAY;
   1313             activeDisplay = null;
   1314         }
   1315         String activeDisplayAddress = activeDisplay != null ?
   1316                 activeDisplay.getDeviceAddress() : null;
   1317 
   1318         // Add or update routes.
   1319         for (int i = 0; i < displays.length; i++) {
   1320             final WifiDisplay d = displays[i];
   1321             if (shouldShowWifiDisplay(d, activeDisplay)) {
   1322                 RouteInfo route = findWifiDisplayRoute(d);
   1323                 if (route == null) {
   1324                     route = makeWifiDisplayRoute(d, status);
   1325                     addRouteStatic(route);
   1326                 } else {
   1327                     String address = d.getDeviceAddress();
   1328                     boolean disconnected = !address.equals(activeDisplayAddress)
   1329                             && address.equals(sStatic.mPreviousActiveWifiDisplayAddress);
   1330                     updateWifiDisplayRoute(route, d, status, disconnected);
   1331                 }
   1332                 if (d.equals(activeDisplay)) {
   1333                     selectRouteStatic(route.getSupportedTypes(), route, false);
   1334                 }
   1335             }
   1336         }
   1337 
   1338         // Remove stale routes.
   1339         for (int i = sStatic.mRoutes.size(); i-- > 0; ) {
   1340             RouteInfo route = sStatic.mRoutes.get(i);
   1341             if (route.mDeviceAddress != null) {
   1342                 WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress);
   1343                 if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) {
   1344                     removeRouteStatic(route);
   1345                 }
   1346             }
   1347         }
   1348 
   1349         // Remember the current active wifi display address so that we can infer disconnections.
   1350         // TODO: This hack will go away once all of this is moved into the media router service.
   1351         sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;
   1352     }
   1353 
   1354     private static boolean shouldShowWifiDisplay(WifiDisplay d, WifiDisplay activeDisplay) {
   1355         return d.isRemembered() || d.equals(activeDisplay);
   1356     }
   1357 
   1358     static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) {
   1359         int newStatus;
   1360         if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) {
   1361             newStatus = RouteInfo.STATUS_SCANNING;
   1362         } else if (d.isAvailable()) {
   1363             newStatus = d.canConnect() ?
   1364                     RouteInfo.STATUS_AVAILABLE: RouteInfo.STATUS_IN_USE;
   1365         } else {
   1366             newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
   1367         }
   1368 
   1369         if (d.equals(wfdStatus.getActiveDisplay())) {
   1370             final int activeState = wfdStatus.getActiveDisplayState();
   1371             switch (activeState) {
   1372                 case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
   1373                     newStatus = RouteInfo.STATUS_CONNECTED;
   1374                     break;
   1375                 case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
   1376                     newStatus = RouteInfo.STATUS_CONNECTING;
   1377                     break;
   1378                 case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
   1379                     Log.e(TAG, "Active display is not connected!");
   1380                     break;
   1381             }
   1382         }
   1383 
   1384         return newStatus;
   1385     }
   1386 
   1387     static boolean isWifiDisplayEnabled(WifiDisplay d, WifiDisplayStatus wfdStatus) {
   1388         return d.isAvailable() && (d.canConnect() || d.equals(wfdStatus.getActiveDisplay()));
   1389     }
   1390 
   1391     static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) {
   1392         final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
   1393         newRoute.mDeviceAddress = display.getDeviceAddress();
   1394         newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
   1395                 | ROUTE_TYPE_REMOTE_DISPLAY;
   1396         newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
   1397         newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
   1398 
   1399         newRoute.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
   1400         newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus);
   1401         newRoute.mName = display.getFriendlyDisplayName();
   1402         newRoute.mDescription = sStatic.mResources.getText(
   1403                 com.android.internal.R.string.wireless_display_route_description);
   1404         newRoute.updatePresentationDisplay();
   1405         return newRoute;
   1406     }
   1407 
   1408     private static void updateWifiDisplayRoute(
   1409             RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus,
   1410             boolean disconnected) {
   1411         boolean changed = false;
   1412         final String newName = display.getFriendlyDisplayName();
   1413         if (!route.getName().equals(newName)) {
   1414             route.mName = newName;
   1415             changed = true;
   1416         }
   1417 
   1418         boolean enabled = isWifiDisplayEnabled(display, wfdStatus);
   1419         changed |= route.mEnabled != enabled;
   1420         route.mEnabled = enabled;
   1421 
   1422         changed |= route.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
   1423 
   1424         if (changed) {
   1425             dispatchRouteChanged(route);
   1426         }
   1427 
   1428         if ((!enabled || disconnected) && route.isSelected()) {
   1429             // Oops, no longer available. Reselect the default.
   1430             selectDefaultRouteStatic();
   1431         }
   1432     }
   1433 
   1434     private static WifiDisplay findWifiDisplay(WifiDisplay[] displays, String deviceAddress) {
   1435         for (int i = 0; i < displays.length; i++) {
   1436             final WifiDisplay d = displays[i];
   1437             if (d.getDeviceAddress().equals(deviceAddress)) {
   1438                 return d;
   1439             }
   1440         }
   1441         return null;
   1442     }
   1443 
   1444     private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
   1445         final int count = sStatic.mRoutes.size();
   1446         for (int i = 0; i < count; i++) {
   1447             final RouteInfo info = sStatic.mRoutes.get(i);
   1448             if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
   1449                 return info;
   1450             }
   1451         }
   1452         return null;
   1453     }
   1454 
   1455     /**
   1456      * Information about a media route.
   1457      */
   1458     public static class RouteInfo {
   1459         CharSequence mName;
   1460         int mNameResId;
   1461         CharSequence mDescription;
   1462         private CharSequence mStatus;
   1463         int mSupportedTypes;
   1464         RouteGroup mGroup;
   1465         final RouteCategory mCategory;
   1466         Drawable mIcon;
   1467         // playback information
   1468         int mPlaybackType = PLAYBACK_TYPE_LOCAL;
   1469         int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
   1470         int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
   1471         int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
   1472         int mPlaybackStream = AudioManager.STREAM_MUSIC;
   1473         VolumeCallbackInfo mVcb;
   1474         Display mPresentationDisplay;
   1475         int mPresentationDisplayId = -1;
   1476 
   1477         String mDeviceAddress;
   1478         boolean mEnabled = true;
   1479 
   1480         // An id by which the route is known to the media router service.
   1481         // Null if this route only exists as an artifact within this process.
   1482         String mGlobalRouteId;
   1483 
   1484         // A predetermined connection status that can override mStatus
   1485         private int mRealStatusCode;
   1486         private int mResolvedStatusCode;
   1487 
   1488         /** @hide */ public static final int STATUS_NONE = 0;
   1489         /** @hide */ public static final int STATUS_SCANNING = 1;
   1490         /** @hide */ public static final int STATUS_CONNECTING = 2;
   1491         /** @hide */ public static final int STATUS_AVAILABLE = 3;
   1492         /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
   1493         /** @hide */ public static final int STATUS_IN_USE = 5;
   1494         /** @hide */ public static final int STATUS_CONNECTED = 6;
   1495 
   1496         private Object mTag;
   1497 
   1498         /**
   1499          * The default playback type, "local", indicating the presentation of the media is happening
   1500          * on the same device (e.g. a phone, a tablet) as where it is controlled from.
   1501          * @see #getPlaybackType()
   1502          */
   1503         public final static int PLAYBACK_TYPE_LOCAL = 0;
   1504         /**
   1505          * A playback type indicating the presentation of the media is happening on
   1506          * a different device (i.e. the remote device) than where it is controlled from.
   1507          * @see #getPlaybackType()
   1508          */
   1509         public final static int PLAYBACK_TYPE_REMOTE = 1;
   1510         /**
   1511          * Playback information indicating the playback volume is fixed, i.e. it cannot be
   1512          * controlled from this object. An example of fixed playback volume is a remote player,
   1513          * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
   1514          * than attenuate at the source.
   1515          * @see #getVolumeHandling()
   1516          */
   1517         public final static int PLAYBACK_VOLUME_FIXED = 0;
   1518         /**
   1519          * Playback information indicating the playback volume is variable and can be controlled
   1520          * from this object.
   1521          * @see #getVolumeHandling()
   1522          */
   1523         public final static int PLAYBACK_VOLUME_VARIABLE = 1;
   1524 
   1525         RouteInfo(RouteCategory category) {
   1526             mCategory = category;
   1527         }
   1528 
   1529         /**
   1530          * Gets the user-visible name of the route.
   1531          * <p>
   1532          * The route name identifies the destination represented by the route.
   1533          * It may be a user-supplied name, an alias, or device serial number.
   1534          * </p>
   1535          *
   1536          * @return The user-visible name of a media route.  This is the string presented
   1537          * to users who may select this as the active route.
   1538          */
   1539         public CharSequence getName() {
   1540             return getName(sStatic.mResources);
   1541         }
   1542 
   1543         /**
   1544          * Return the properly localized/resource user-visible name of this route.
   1545          * <p>
   1546          * The route name identifies the destination represented by the route.
   1547          * It may be a user-supplied name, an alias, or device serial number.
   1548          * </p>
   1549          *
   1550          * @param context Context used to resolve the correct configuration to load
   1551          * @return The user-visible name of a media route.  This is the string presented
   1552          * to users who may select this as the active route.
   1553          */
   1554         public CharSequence getName(Context context) {
   1555             return getName(context.getResources());
   1556         }
   1557 
   1558         CharSequence getName(Resources res) {
   1559             if (mNameResId != 0) {
   1560                 return mName = res.getText(mNameResId);
   1561             }
   1562             return mName;
   1563         }
   1564 
   1565         /**
   1566          * Gets the user-visible description of the route.
   1567          * <p>
   1568          * The route description describes the kind of destination represented by the route.
   1569          * It may be a user-supplied string, a model number or brand of device.
   1570          * </p>
   1571          *
   1572          * @return The description of the route, or null if none.
   1573          */
   1574         public CharSequence getDescription() {
   1575             return mDescription;
   1576         }
   1577 
   1578         /**
   1579          * @return The user-visible status for a media route. This may include a description
   1580          * of the currently playing media, if available.
   1581          */
   1582         public CharSequence getStatus() {
   1583             return mStatus;
   1584         }
   1585 
   1586         /**
   1587          * Set this route's status by predetermined status code. If the caller
   1588          * should dispatch a route changed event this call will return true;
   1589          */
   1590         boolean setRealStatusCode(int statusCode) {
   1591             if (mRealStatusCode != statusCode) {
   1592                 mRealStatusCode = statusCode;
   1593                 return resolveStatusCode();
   1594             }
   1595             return false;
   1596         }
   1597 
   1598         /**
   1599          * Resolves the status code whenever the real status code or selection state
   1600          * changes.
   1601          */
   1602         boolean resolveStatusCode() {
   1603             int statusCode = mRealStatusCode;
   1604             if (isSelected()) {
   1605                 switch (statusCode) {
   1606                     // If the route is selected and its status appears to be between states
   1607                     // then report it as connecting even though it has not yet had a chance
   1608                     // to officially move into the CONNECTING state.  Note that routes in
   1609                     // the NONE state are assumed to not require an explicit connection
   1610                     // lifecycle whereas those that are AVAILABLE are assumed to have
   1611                     // to eventually proceed to CONNECTED.
   1612                     case STATUS_AVAILABLE:
   1613                     case STATUS_SCANNING:
   1614                         statusCode = STATUS_CONNECTING;
   1615                         break;
   1616                 }
   1617             }
   1618             if (mResolvedStatusCode == statusCode) {
   1619                 return false;
   1620             }
   1621 
   1622             mResolvedStatusCode = statusCode;
   1623             int resId;
   1624             switch (statusCode) {
   1625                 case STATUS_SCANNING:
   1626                     resId = com.android.internal.R.string.media_route_status_scanning;
   1627                     break;
   1628                 case STATUS_CONNECTING:
   1629                     resId = com.android.internal.R.string.media_route_status_connecting;
   1630                     break;
   1631                 case STATUS_AVAILABLE:
   1632                     resId = com.android.internal.R.string.media_route_status_available;
   1633                     break;
   1634                 case STATUS_NOT_AVAILABLE:
   1635                     resId = com.android.internal.R.string.media_route_status_not_available;
   1636                     break;
   1637                 case STATUS_IN_USE:
   1638                     resId = com.android.internal.R.string.media_route_status_in_use;
   1639                     break;
   1640                 case STATUS_CONNECTED:
   1641                 case STATUS_NONE:
   1642                 default:
   1643                     resId = 0;
   1644                     break;
   1645             }
   1646             mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
   1647             return true;
   1648         }
   1649 
   1650         /**
   1651          * @hide
   1652          */
   1653         public int getStatusCode() {
   1654             return mResolvedStatusCode;
   1655         }
   1656 
   1657         /**
   1658          * @return A media type flag set describing which types this route supports.
   1659          */
   1660         public int getSupportedTypes() {
   1661             return mSupportedTypes;
   1662         }
   1663 
   1664         /** @hide */
   1665         public boolean matchesTypes(int types) {
   1666             return (mSupportedTypes & types) != 0;
   1667         }
   1668 
   1669         /**
   1670          * @return The group that this route belongs to.
   1671          */
   1672         public RouteGroup getGroup() {
   1673             return mGroup;
   1674         }
   1675 
   1676         /**
   1677          * @return the category this route belongs to.
   1678          */
   1679         public RouteCategory getCategory() {
   1680             return mCategory;
   1681         }
   1682 
   1683         /**
   1684          * Get the icon representing this route.
   1685          * This icon will be used in picker UIs if available.
   1686          *
   1687          * @return the icon representing this route or null if no icon is available
   1688          */
   1689         public Drawable getIconDrawable() {
   1690             return mIcon;
   1691         }
   1692 
   1693         /**
   1694          * Set an application-specific tag object for this route.
   1695          * The application may use this to store arbitrary data associated with the
   1696          * route for internal tracking.
   1697          *
   1698          * <p>Note that the lifespan of a route may be well past the lifespan of
   1699          * an Activity or other Context; take care that objects you store here
   1700          * will not keep more data in memory alive than you intend.</p>
   1701          *
   1702          * @param tag Arbitrary, app-specific data for this route to hold for later use
   1703          */
   1704         public void setTag(Object tag) {
   1705             mTag = tag;
   1706             routeUpdated();
   1707         }
   1708 
   1709         /**
   1710          * @return The tag object previously set by the application
   1711          * @see #setTag(Object)
   1712          */
   1713         public Object getTag() {
   1714             return mTag;
   1715         }
   1716 
   1717         /**
   1718          * @return the type of playback associated with this route
   1719          * @see UserRouteInfo#setPlaybackType(int)
   1720          */
   1721         public int getPlaybackType() {
   1722             return mPlaybackType;
   1723         }
   1724 
   1725         /**
   1726          * @return the stream over which the playback associated with this route is performed
   1727          * @see UserRouteInfo#setPlaybackStream(int)
   1728          */
   1729         public int getPlaybackStream() {
   1730             return mPlaybackStream;
   1731         }
   1732 
   1733         /**
   1734          * Return the current volume for this route. Depending on the route, this may only
   1735          * be valid if the route is currently selected.
   1736          *
   1737          * @return the volume at which the playback associated with this route is performed
   1738          * @see UserRouteInfo#setVolume(int)
   1739          */
   1740         public int getVolume() {
   1741             if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
   1742                 int vol = 0;
   1743                 try {
   1744                     vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream);
   1745                 } catch (RemoteException e) {
   1746                     Log.e(TAG, "Error getting local stream volume", e);
   1747                 }
   1748                 return vol;
   1749             } else {
   1750                 return mVolume;
   1751             }
   1752         }
   1753 
   1754         /**
   1755          * Request a volume change for this route.
   1756          * @param volume value between 0 and getVolumeMax
   1757          */
   1758         public void requestSetVolume(int volume) {
   1759             if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
   1760                 try {
   1761                     sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0,
   1762                             ActivityThread.currentPackageName());
   1763                 } catch (RemoteException e) {
   1764                     Log.e(TAG, "Error setting local stream volume", e);
   1765                 }
   1766             } else {
   1767                 sStatic.requestSetVolume(this, volume);
   1768             }
   1769         }
   1770 
   1771         /**
   1772          * Request an incremental volume update for this route.
   1773          * @param direction Delta to apply to the current volume
   1774          */
   1775         public void requestUpdateVolume(int direction) {
   1776             if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
   1777                 try {
   1778                     final int volume =
   1779                             Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
   1780                     sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0,
   1781                             ActivityThread.currentPackageName());
   1782                 } catch (RemoteException e) {
   1783                     Log.e(TAG, "Error setting local stream volume", e);
   1784                 }
   1785             } else {
   1786                 sStatic.requestUpdateVolume(this, direction);
   1787             }
   1788         }
   1789 
   1790         /**
   1791          * @return the maximum volume at which the playback associated with this route is performed
   1792          * @see UserRouteInfo#setVolumeMax(int)
   1793          */
   1794         public int getVolumeMax() {
   1795             if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
   1796                 int volMax = 0;
   1797                 try {
   1798                     volMax = sStatic.mAudioService.getStreamMaxVolume(mPlaybackStream);
   1799                 } catch (RemoteException e) {
   1800                     Log.e(TAG, "Error getting local stream volume", e);
   1801                 }
   1802                 return volMax;
   1803             } else {
   1804                 return mVolumeMax;
   1805             }
   1806         }
   1807 
   1808         /**
   1809          * @return how volume is handling on the route
   1810          * @see UserRouteInfo#setVolumeHandling(int)
   1811          */
   1812         public int getVolumeHandling() {
   1813             return mVolumeHandling;
   1814         }
   1815 
   1816         /**
   1817          * Gets the {@link Display} that should be used by the application to show
   1818          * a {@link android.app.Presentation} on an external display when this route is selected.
   1819          * Depending on the route, this may only be valid if the route is currently
   1820          * selected.
   1821          * <p>
   1822          * The preferred presentation display may change independently of the route
   1823          * being selected or unselected.  For example, the presentation display
   1824          * of the default system route may change when an external HDMI display is connected
   1825          * or disconnected even though the route itself has not changed.
   1826          * </p><p>
   1827          * This method may return null if there is no external display associated with
   1828          * the route or if the display is not ready to show UI yet.
   1829          * </p><p>
   1830          * The application should listen for changes to the presentation display
   1831          * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
   1832          * show or dismiss its {@link android.app.Presentation} accordingly when the display
   1833          * becomes available or is removed.
   1834          * </p><p>
   1835          * This method only makes sense for {@link #ROUTE_TYPE_LIVE_VIDEO live video} routes.
   1836          * </p>
   1837          *
   1838          * @return The preferred presentation display to use when this route is
   1839          * selected or null if none.
   1840          *
   1841          * @see #ROUTE_TYPE_LIVE_VIDEO
   1842          * @see android.app.Presentation
   1843          */
   1844         public Display getPresentationDisplay() {
   1845             return mPresentationDisplay;
   1846         }
   1847 
   1848         boolean updatePresentationDisplay() {
   1849             Display display = choosePresentationDisplay();
   1850             if (mPresentationDisplay != display) {
   1851                 mPresentationDisplay = display;
   1852                 return true;
   1853             }
   1854             return false;
   1855         }
   1856 
   1857         private Display choosePresentationDisplay() {
   1858             if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
   1859                 Display[] displays = sStatic.getAllPresentationDisplays();
   1860 
   1861                 // Ensure that the specified display is valid for presentations.
   1862                 // This check will normally disallow the default display unless it was
   1863                 // configured as a presentation display for some reason.
   1864                 if (mPresentationDisplayId >= 0) {
   1865                     for (Display display : displays) {
   1866                         if (display.getDisplayId() == mPresentationDisplayId) {
   1867                             return display;
   1868                         }
   1869                     }
   1870                     return null;
   1871                 }
   1872 
   1873                 // Find the indicated Wifi display by its address.
   1874                 if (mDeviceAddress != null) {
   1875                     for (Display display : displays) {
   1876                         if (display.getType() == Display.TYPE_WIFI
   1877                                 && mDeviceAddress.equals(display.getAddress())) {
   1878                             return display;
   1879                         }
   1880                     }
   1881                     return null;
   1882                 }
   1883 
   1884                 // For the default route, choose the first presentation display from the list.
   1885                 if (this == sStatic.mDefaultAudioVideo && displays.length > 0) {
   1886                     return displays[0];
   1887                 }
   1888             }
   1889             return null;
   1890         }
   1891 
   1892         /** @hide */
   1893         public String getDeviceAddress() {
   1894             return mDeviceAddress;
   1895         }
   1896 
   1897         /**
   1898          * Returns true if this route is enabled and may be selected.
   1899          *
   1900          * @return True if this route is enabled.
   1901          */
   1902         public boolean isEnabled() {
   1903             return mEnabled;
   1904         }
   1905 
   1906         /**
   1907          * Returns true if the route is in the process of connecting and is not
   1908          * yet ready for use.
   1909          *
   1910          * @return True if this route is in the process of connecting.
   1911          */
   1912         public boolean isConnecting() {
   1913             return mResolvedStatusCode == STATUS_CONNECTING;
   1914         }
   1915 
   1916         /** @hide */
   1917         public boolean isSelected() {
   1918             return this == sStatic.mSelectedRoute;
   1919         }
   1920 
   1921         /** @hide */
   1922         public boolean isDefault() {
   1923             return this == sStatic.mDefaultAudioVideo;
   1924         }
   1925 
   1926         /** @hide */
   1927         public void select() {
   1928             selectRouteStatic(mSupportedTypes, this, true);
   1929         }
   1930 
   1931         void setStatusInt(CharSequence status) {
   1932             if (!status.equals(mStatus)) {
   1933                 mStatus = status;
   1934                 if (mGroup != null) {
   1935                     mGroup.memberStatusChanged(this, status);
   1936                 }
   1937                 routeUpdated();
   1938             }
   1939         }
   1940 
   1941         final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
   1942             @Override
   1943             public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
   1944                 sStatic.mHandler.post(new Runnable() {
   1945                     @Override
   1946                     public void run() {
   1947                         if (mVcb != null) {
   1948                             if (direction != 0) {
   1949                                 mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
   1950                             } else {
   1951                                 mVcb.vcb.onVolumeSetRequest(mVcb.route, value);
   1952                             }
   1953                         }
   1954                     }
   1955                 });
   1956             }
   1957         };
   1958 
   1959         void routeUpdated() {
   1960             updateRoute(this);
   1961         }
   1962 
   1963         @Override
   1964         public String toString() {
   1965             String supportedTypes = typesToString(getSupportedTypes());
   1966             return getClass().getSimpleName() + "{ name=" + getName() +
   1967                     ", description=" + getDescription() +
   1968                     ", status=" + getStatus() +
   1969                     ", category=" + getCategory() +
   1970                     ", supportedTypes=" + supportedTypes +
   1971                     ", presentationDisplay=" + mPresentationDisplay + " }";
   1972         }
   1973     }
   1974 
   1975     /**
   1976      * Information about a route that the application may define and modify.
   1977      * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
   1978      * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
   1979      *
   1980      * @see MediaRouter.RouteInfo
   1981      */
   1982     public static class UserRouteInfo extends RouteInfo {
   1983         RemoteControlClient mRcc;
   1984 
   1985         UserRouteInfo(RouteCategory category) {
   1986             super(category);
   1987             mSupportedTypes = ROUTE_TYPE_USER;
   1988             mPlaybackType = PLAYBACK_TYPE_REMOTE;
   1989             mVolumeHandling = PLAYBACK_VOLUME_FIXED;
   1990         }
   1991 
   1992         /**
   1993          * Set the user-visible name of this route.
   1994          * @param name Name to display to the user to describe this route
   1995          */
   1996         public void setName(CharSequence name) {
   1997             mName = name;
   1998             routeUpdated();
   1999         }
   2000 
   2001         /**
   2002          * Set the user-visible name of this route.
   2003          * <p>
   2004          * The route name identifies the destination represented by the route.
   2005          * It may be a user-supplied name, an alias, or device serial number.
   2006          * </p>
   2007          *
   2008          * @param resId Resource ID of the name to display to the user to describe this route
   2009          */
   2010         public void setName(int resId) {
   2011             mNameResId = resId;
   2012             mName = null;
   2013             routeUpdated();
   2014         }
   2015 
   2016         /**
   2017          * Set the user-visible description of this route.
   2018          * <p>
   2019          * The route description describes the kind of destination represented by the route.
   2020          * It may be a user-supplied string, a model number or brand of device.
   2021          * </p>
   2022          *
   2023          * @param description The description of the route, or null if none.
   2024          */
   2025         public void setDescription(CharSequence description) {
   2026             mDescription = description;
   2027             routeUpdated();
   2028         }
   2029 
   2030         /**
   2031          * Set the current user-visible status for this route.
   2032          * @param status Status to display to the user to describe what the endpoint
   2033          * of this route is currently doing
   2034          */
   2035         public void setStatus(CharSequence status) {
   2036             setStatusInt(status);
   2037         }
   2038 
   2039         /**
   2040          * Set the RemoteControlClient responsible for reporting playback info for this
   2041          * user route.
   2042          *
   2043          * <p>If this route manages remote playback, the data exposed by this
   2044          * RemoteControlClient will be used to reflect and update information
   2045          * such as route volume info in related UIs.</p>
   2046          *
   2047          * <p>The RemoteControlClient must have been previously registered with
   2048          * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.</p>
   2049          *
   2050          * @param rcc RemoteControlClient associated with this route
   2051          */
   2052         public void setRemoteControlClient(RemoteControlClient rcc) {
   2053             mRcc = rcc;
   2054             updatePlaybackInfoOnRcc();
   2055         }
   2056 
   2057         /**
   2058          * Retrieve the RemoteControlClient associated with this route, if one has been set.
   2059          *
   2060          * @return the RemoteControlClient associated with this route
   2061          * @see #setRemoteControlClient(RemoteControlClient)
   2062          */
   2063         public RemoteControlClient getRemoteControlClient() {
   2064             return mRcc;
   2065         }
   2066 
   2067         /**
   2068          * Set an icon that will be used to represent this route.
   2069          * The system may use this icon in picker UIs or similar.
   2070          *
   2071          * @param icon icon drawable to use to represent this route
   2072          */
   2073         public void setIconDrawable(Drawable icon) {
   2074             mIcon = icon;
   2075         }
   2076 
   2077         /**
   2078          * Set an icon that will be used to represent this route.
   2079          * The system may use this icon in picker UIs or similar.
   2080          *
   2081          * @param resId Resource ID of an icon drawable to use to represent this route
   2082          */
   2083         public void setIconResource(int resId) {
   2084             setIconDrawable(sStatic.mResources.getDrawable(resId));
   2085         }
   2086 
   2087         /**
   2088          * Set a callback to be notified of volume update requests
   2089          * @param vcb
   2090          */
   2091         public void setVolumeCallback(VolumeCallback vcb) {
   2092             mVcb = new VolumeCallbackInfo(vcb, this);
   2093         }
   2094 
   2095         /**
   2096          * Defines whether playback associated with this route is "local"
   2097          *    ({@link RouteInfo#PLAYBACK_TYPE_LOCAL}) or "remote"
   2098          *    ({@link RouteInfo#PLAYBACK_TYPE_REMOTE}).
   2099          * @param type
   2100          */
   2101         public void setPlaybackType(int type) {
   2102             if (mPlaybackType != type) {
   2103                 mPlaybackType = type;
   2104                 setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, type);
   2105             }
   2106         }
   2107 
   2108         /**
   2109          * Defines whether volume for the playback associated with this route is fixed
   2110          * ({@link RouteInfo#PLAYBACK_VOLUME_FIXED}) or can modified
   2111          * ({@link RouteInfo#PLAYBACK_VOLUME_VARIABLE}).
   2112          * @param volumeHandling
   2113          */
   2114         public void setVolumeHandling(int volumeHandling) {
   2115             if (mVolumeHandling != volumeHandling) {
   2116                 mVolumeHandling = volumeHandling;
   2117                 setPlaybackInfoOnRcc(
   2118                         RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, volumeHandling);
   2119             }
   2120         }
   2121 
   2122         /**
   2123          * Defines at what volume the playback associated with this route is performed (for user
   2124          * feedback purposes). This information is only used when the playback is not local.
   2125          * @param volume
   2126          */
   2127         public void setVolume(int volume) {
   2128             volume = Math.max(0, Math.min(volume, getVolumeMax()));
   2129             if (mVolume != volume) {
   2130                 mVolume = volume;
   2131                 setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
   2132                 dispatchRouteVolumeChanged(this);
   2133                 if (mGroup != null) {
   2134                     mGroup.memberVolumeChanged(this);
   2135                 }
   2136             }
   2137         }
   2138 
   2139         @Override
   2140         public void requestSetVolume(int volume) {
   2141             if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
   2142                 if (mVcb == null) {
   2143                     Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
   2144                     return;
   2145                 }
   2146                 mVcb.vcb.onVolumeSetRequest(this, volume);
   2147             }
   2148         }
   2149 
   2150         @Override
   2151         public void requestUpdateVolume(int direction) {
   2152             if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
   2153                 if (mVcb == null) {
   2154                     Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
   2155                     return;
   2156                 }
   2157                 mVcb.vcb.onVolumeUpdateRequest(this, direction);
   2158             }
   2159         }
   2160 
   2161         /**
   2162          * Defines the maximum volume at which the playback associated with this route is performed
   2163          * (for user feedback purposes). This information is only used when the playback is not
   2164          * local.
   2165          * @param volumeMax
   2166          */
   2167         public void setVolumeMax(int volumeMax) {
   2168             if (mVolumeMax != volumeMax) {
   2169                 mVolumeMax = volumeMax;
   2170                 setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, volumeMax);
   2171             }
   2172         }
   2173 
   2174         /**
   2175          * Defines over what stream type the media is presented.
   2176          * @param stream
   2177          */
   2178         public void setPlaybackStream(int stream) {
   2179             if (mPlaybackStream != stream) {
   2180                 mPlaybackStream = stream;
   2181                 setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_USES_STREAM, stream);
   2182             }
   2183         }
   2184 
   2185         private void updatePlaybackInfoOnRcc() {
   2186             if ((mRcc != null) && (mRcc.getRcseId() != RemoteControlClient.RCSE_ID_UNREGISTERED)) {
   2187                 mRcc.setPlaybackInformation(
   2188                         RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, mVolumeMax);
   2189                 mRcc.setPlaybackInformation(
   2190                         RemoteControlClient.PLAYBACKINFO_VOLUME, mVolume);
   2191                 mRcc.setPlaybackInformation(
   2192                         RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, mVolumeHandling);
   2193                 mRcc.setPlaybackInformation(
   2194                         RemoteControlClient.PLAYBACKINFO_USES_STREAM, mPlaybackStream);
   2195                 mRcc.setPlaybackInformation(
   2196                         RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, mPlaybackType);
   2197                 // let AudioService know whom to call when remote volume needs to be updated
   2198                 try {
   2199                     sStatic.mAudioService.registerRemoteVolumeObserverForRcc(
   2200                             mRcc.getRcseId() /* rccId */, mRemoteVolObserver /* rvo */);
   2201                 } catch (RemoteException e) {
   2202                     Log.e(TAG, "Error registering remote volume observer", e);
   2203                 }
   2204             }
   2205         }
   2206 
   2207         private void setPlaybackInfoOnRcc(int what, int value) {
   2208             if (mRcc != null) {
   2209                 mRcc.setPlaybackInformation(what, value);
   2210             }
   2211         }
   2212     }
   2213 
   2214     /**
   2215      * Information about a route that consists of multiple other routes in a group.
   2216      */
   2217     public static class RouteGroup extends RouteInfo {
   2218         final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
   2219         private boolean mUpdateName;
   2220 
   2221         RouteGroup(RouteCategory category) {
   2222             super(category);
   2223             mGroup = this;
   2224             mVolumeHandling = PLAYBACK_VOLUME_FIXED;
   2225         }
   2226 
   2227         @Override
   2228         CharSequence getName(Resources res) {
   2229             if (mUpdateName) updateName();
   2230             return super.getName(res);
   2231         }
   2232 
   2233         /**
   2234          * Add a route to this group. The route must not currently belong to another group.
   2235          *
   2236          * @param route route to add to this group
   2237          */
   2238         public void addRoute(RouteInfo route) {
   2239             if (route.getGroup() != null) {
   2240                 throw new IllegalStateException("Route " + route + " is already part of a group.");
   2241             }
   2242             if (route.getCategory() != mCategory) {
   2243                 throw new IllegalArgumentException(
   2244                         "Route cannot be added to a group with a different category. " +
   2245                             "(Route category=" + route.getCategory() +
   2246                             " group category=" + mCategory + ")");
   2247             }
   2248             final int at = mRoutes.size();
   2249             mRoutes.add(route);
   2250             route.mGroup = this;
   2251             mUpdateName = true;
   2252             updateVolume();
   2253             routeUpdated();
   2254             dispatchRouteGrouped(route, this, at);
   2255         }
   2256 
   2257         /**
   2258          * Add a route to this group before the specified index.
   2259          *
   2260          * @param route route to add
   2261          * @param insertAt insert the new route before this index
   2262          */
   2263         public void addRoute(RouteInfo route, int insertAt) {
   2264             if (route.getGroup() != null) {
   2265                 throw new IllegalStateException("Route " + route + " is already part of a group.");
   2266             }
   2267             if (route.getCategory() != mCategory) {
   2268                 throw new IllegalArgumentException(
   2269                         "Route cannot be added to a group with a different category. " +
   2270                             "(Route category=" + route.getCategory() +
   2271                             " group category=" + mCategory + ")");
   2272             }
   2273             mRoutes.add(insertAt, route);
   2274             route.mGroup = this;
   2275             mUpdateName = true;
   2276             updateVolume();
   2277             routeUpdated();
   2278             dispatchRouteGrouped(route, this, insertAt);
   2279         }
   2280 
   2281         /**
   2282          * Remove a route from this group.
   2283          *
   2284          * @param route route to remove
   2285          */
   2286         public void removeRoute(RouteInfo route) {
   2287             if (route.getGroup() != this) {
   2288                 throw new IllegalArgumentException("Route " + route +
   2289                         " is not a member of this group.");
   2290             }
   2291             mRoutes.remove(route);
   2292             route.mGroup = null;
   2293             mUpdateName = true;
   2294             updateVolume();
   2295             dispatchRouteUngrouped(route, this);
   2296             routeUpdated();
   2297         }
   2298 
   2299         /**
   2300          * Remove the route at the specified index from this group.
   2301          *
   2302          * @param index index of the route to remove
   2303          */
   2304         public void removeRoute(int index) {
   2305             RouteInfo route = mRoutes.remove(index);
   2306             route.mGroup = null;
   2307             mUpdateName = true;
   2308             updateVolume();
   2309             dispatchRouteUngrouped(route, this);
   2310             routeUpdated();
   2311         }
   2312 
   2313         /**
   2314          * @return The number of routes in this group
   2315          */
   2316         public int getRouteCount() {
   2317             return mRoutes.size();
   2318         }
   2319 
   2320         /**
   2321          * Return the route in this group at the specified index
   2322          *
   2323          * @param index Index to fetch
   2324          * @return The route at index
   2325          */
   2326         public RouteInfo getRouteAt(int index) {
   2327             return mRoutes.get(index);
   2328         }
   2329 
   2330         /**
   2331          * Set an icon that will be used to represent this group.
   2332          * The system may use this icon in picker UIs or similar.
   2333          *
   2334          * @param icon icon drawable to use to represent this group
   2335          */
   2336         public void setIconDrawable(Drawable icon) {
   2337             mIcon = icon;
   2338         }
   2339 
   2340         /**
   2341          * Set an icon that will be used to represent this group.
   2342          * The system may use this icon in picker UIs or similar.
   2343          *
   2344          * @param resId Resource ID of an icon drawable to use to represent this group
   2345          */
   2346         public void setIconResource(int resId) {
   2347             setIconDrawable(sStatic.mResources.getDrawable(resId));
   2348         }
   2349 
   2350         @Override
   2351         public void requestSetVolume(int volume) {
   2352             final int maxVol = getVolumeMax();
   2353             if (maxVol == 0) {
   2354                 return;
   2355             }
   2356 
   2357             final float scaledVolume = (float) volume / maxVol;
   2358             final int routeCount = getRouteCount();
   2359             for (int i = 0; i < routeCount; i++) {
   2360                 final RouteInfo route = getRouteAt(i);
   2361                 final int routeVol = (int) (scaledVolume * route.getVolumeMax());
   2362                 route.requestSetVolume(routeVol);
   2363             }
   2364             if (volume != mVolume) {
   2365                 mVolume = volume;
   2366                 dispatchRouteVolumeChanged(this);
   2367             }
   2368         }
   2369 
   2370         @Override
   2371         public void requestUpdateVolume(int direction) {
   2372             final int maxVol = getVolumeMax();
   2373             if (maxVol == 0) {
   2374                 return;
   2375             }
   2376 
   2377             final int routeCount = getRouteCount();
   2378             int volume = 0;
   2379             for (int i = 0; i < routeCount; i++) {
   2380                 final RouteInfo route = getRouteAt(i);
   2381                 route.requestUpdateVolume(direction);
   2382                 final int routeVol = route.getVolume();
   2383                 if (routeVol > volume) {
   2384                     volume = routeVol;
   2385                 }
   2386             }
   2387             if (volume != mVolume) {
   2388                 mVolume = volume;
   2389                 dispatchRouteVolumeChanged(this);
   2390             }
   2391         }
   2392 
   2393         void memberNameChanged(RouteInfo info, CharSequence name) {
   2394             mUpdateName = true;
   2395             routeUpdated();
   2396         }
   2397 
   2398         void memberStatusChanged(RouteInfo info, CharSequence status) {
   2399             setStatusInt(status);
   2400         }
   2401 
   2402         void memberVolumeChanged(RouteInfo info) {
   2403             updateVolume();
   2404         }
   2405 
   2406         void updateVolume() {
   2407             // A group always represents the highest component volume value.
   2408             final int routeCount = getRouteCount();
   2409             int volume = 0;
   2410             for (int i = 0; i < routeCount; i++) {
   2411                 final int routeVol = getRouteAt(i).getVolume();
   2412                 if (routeVol > volume) {
   2413                     volume = routeVol;
   2414                 }
   2415             }
   2416             if (volume != mVolume) {
   2417                 mVolume = volume;
   2418                 dispatchRouteVolumeChanged(this);
   2419             }
   2420         }
   2421 
   2422         @Override
   2423         void routeUpdated() {
   2424             int types = 0;
   2425             final int count = mRoutes.size();
   2426             if (count == 0) {
   2427                 // Don't keep empty groups in the router.
   2428                 MediaRouter.removeRouteStatic(this);
   2429                 return;
   2430             }
   2431 
   2432             int maxVolume = 0;
   2433             boolean isLocal = true;
   2434             boolean isFixedVolume = true;
   2435             for (int i = 0; i < count; i++) {
   2436                 final RouteInfo route = mRoutes.get(i);
   2437                 types |= route.mSupportedTypes;
   2438                 final int routeMaxVolume = route.getVolumeMax();
   2439                 if (routeMaxVolume > maxVolume) {
   2440                     maxVolume = routeMaxVolume;
   2441                 }
   2442                 isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
   2443                 isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
   2444             }
   2445             mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
   2446             mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
   2447             mSupportedTypes = types;
   2448             mVolumeMax = maxVolume;
   2449             mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
   2450             super.routeUpdated();
   2451         }
   2452 
   2453         void updateName() {
   2454             final StringBuilder sb = new StringBuilder();
   2455             final int count = mRoutes.size();
   2456             for (int i = 0; i < count; i++) {
   2457                 final RouteInfo info = mRoutes.get(i);
   2458                 // TODO: There's probably a much more correct way to localize this.
   2459                 if (i > 0) sb.append(", ");
   2460                 sb.append(info.mName);
   2461             }
   2462             mName = sb.toString();
   2463             mUpdateName = false;
   2464         }
   2465 
   2466         @Override
   2467         public String toString() {
   2468             StringBuilder sb = new StringBuilder(super.toString());
   2469             sb.append('[');
   2470             final int count = mRoutes.size();
   2471             for (int i = 0; i < count; i++) {
   2472                 if (i > 0) sb.append(", ");
   2473                 sb.append(mRoutes.get(i));
   2474             }
   2475             sb.append(']');
   2476             return sb.toString();
   2477         }
   2478     }
   2479 
   2480     /**
   2481      * Definition of a category of routes. All routes belong to a category.
   2482      */
   2483     public static class RouteCategory {
   2484         CharSequence mName;
   2485         int mNameResId;
   2486         int mTypes;
   2487         final boolean mGroupable;
   2488         boolean mIsSystem;
   2489 
   2490         RouteCategory(CharSequence name, int types, boolean groupable) {
   2491             mName = name;
   2492             mTypes = types;
   2493             mGroupable = groupable;
   2494         }
   2495 
   2496         RouteCategory(int nameResId, int types, boolean groupable) {
   2497             mNameResId = nameResId;
   2498             mTypes = types;
   2499             mGroupable = groupable;
   2500         }
   2501 
   2502         /**
   2503          * @return the name of this route category
   2504          */
   2505         public CharSequence getName() {
   2506             return getName(sStatic.mResources);
   2507         }
   2508 
   2509         /**
   2510          * Return the properly localized/configuration dependent name of this RouteCategory.
   2511          *
   2512          * @param context Context to resolve name resources
   2513          * @return the name of this route category
   2514          */
   2515         public CharSequence getName(Context context) {
   2516             return getName(context.getResources());
   2517         }
   2518 
   2519         CharSequence getName(Resources res) {
   2520             if (mNameResId != 0) {
   2521                 return res.getText(mNameResId);
   2522             }
   2523             return mName;
   2524         }
   2525 
   2526         /**
   2527          * Return the current list of routes in this category that have been added
   2528          * to the MediaRouter.
   2529          *
   2530          * <p>This list will not include routes that are nested within RouteGroups.
   2531          * A RouteGroup is treated as a single route within its category.</p>
   2532          *
   2533          * @param out a List to fill with the routes in this category. If this parameter is
   2534          *            non-null, it will be cleared, filled with the current routes with this
   2535          *            category, and returned. If this parameter is null, a new List will be
   2536          *            allocated to report the category's current routes.
   2537          * @return A list with the routes in this category that have been added to the MediaRouter.
   2538          */
   2539         public List<RouteInfo> getRoutes(List<RouteInfo> out) {
   2540             if (out == null) {
   2541                 out = new ArrayList<RouteInfo>();
   2542             } else {
   2543                 out.clear();
   2544             }
   2545 
   2546             final int count = getRouteCountStatic();
   2547             for (int i = 0; i < count; i++) {
   2548                 final RouteInfo route = getRouteAtStatic(i);
   2549                 if (route.mCategory == this) {
   2550                     out.add(route);
   2551                 }
   2552             }
   2553             return out;
   2554         }
   2555 
   2556         /**
   2557          * @return Flag set describing the route types supported by this category
   2558          */
   2559         public int getSupportedTypes() {
   2560             return mTypes;
   2561         }
   2562 
   2563         /**
   2564          * Return whether or not this category supports grouping.
   2565          *
   2566          * <p>If this method returns true, all routes obtained from this category
   2567          * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
   2568          *
   2569          * @return true if this category supports
   2570          */
   2571         public boolean isGroupable() {
   2572             return mGroupable;
   2573         }
   2574 
   2575         /**
   2576          * @return true if this is the category reserved for system routes.
   2577          * @hide
   2578          */
   2579         public boolean isSystem() {
   2580             return mIsSystem;
   2581         }
   2582 
   2583         @Override
   2584         public String toString() {
   2585             return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
   2586                     " groupable=" + mGroupable + " }";
   2587         }
   2588     }
   2589 
   2590     static class CallbackInfo {
   2591         public int type;
   2592         public int flags;
   2593         public final Callback cb;
   2594         public final MediaRouter router;
   2595 
   2596         public CallbackInfo(Callback cb, int type, int flags, MediaRouter router) {
   2597             this.cb = cb;
   2598             this.type = type;
   2599             this.flags = flags;
   2600             this.router = router;
   2601         }
   2602 
   2603         public boolean filterRouteEvent(RouteInfo route) {
   2604             return filterRouteEvent(route.mSupportedTypes);
   2605         }
   2606 
   2607         public boolean filterRouteEvent(int supportedTypes) {
   2608             return (flags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
   2609                     || (type & supportedTypes) != 0;
   2610         }
   2611     }
   2612 
   2613     /**
   2614      * Interface for receiving events about media routing changes.
   2615      * All methods of this interface will be called from the application's main thread.
   2616      * <p>
   2617      * A Callback will only receive events relevant to routes that the callback
   2618      * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
   2619      * flag was specified in {@link MediaRouter#addCallback(int, Callback, int)}.
   2620      * </p>
   2621      *
   2622      * @see MediaRouter#addCallback(int, Callback, int)
   2623      * @see MediaRouter#removeCallback(Callback)
   2624      */
   2625     public static abstract class Callback {
   2626         /**
   2627          * Called when the supplied route becomes selected as the active route
   2628          * for the given route type.
   2629          *
   2630          * @param router the MediaRouter reporting the event
   2631          * @param type Type flag set indicating the routes that have been selected
   2632          * @param info Route that has been selected for the given route types
   2633          */
   2634         public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
   2635 
   2636         /**
   2637          * Called when the supplied route becomes unselected as the active route
   2638          * for the given route type.
   2639          *
   2640          * @param router the MediaRouter reporting the event
   2641          * @param type Type flag set indicating the routes that have been unselected
   2642          * @param info Route that has been unselected for the given route types
   2643          */
   2644         public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
   2645 
   2646         /**
   2647          * Called when a route for the specified type was added.
   2648          *
   2649          * @param router the MediaRouter reporting the event
   2650          * @param info Route that has become available for use
   2651          */
   2652         public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
   2653 
   2654         /**
   2655          * Called when a route for the specified type was removed.
   2656          *
   2657          * @param router the MediaRouter reporting the event
   2658          * @param info Route that has been removed from availability
   2659          */
   2660         public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
   2661 
   2662         /**
   2663          * Called when an aspect of the indicated route has changed.
   2664          *
   2665          * <p>This will not indicate that the types supported by this route have
   2666          * changed, only that cosmetic info such as name or status have been updated.</p>
   2667          *
   2668          * @param router the MediaRouter reporting the event
   2669          * @param info The route that was changed
   2670          */
   2671         public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
   2672 
   2673         /**
   2674          * Called when a route is added to a group.
   2675          *
   2676          * @param router the MediaRouter reporting the event
   2677          * @param info The route that was added
   2678          * @param group The group the route was added to
   2679          * @param index The route index within group that info was added at
   2680          */
   2681         public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
   2682                 int index);
   2683 
   2684         /**
   2685          * Called when a route is removed from a group.
   2686          *
   2687          * @param router the MediaRouter reporting the event
   2688          * @param info The route that was removed
   2689          * @param group The group the route was removed from
   2690          */
   2691         public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
   2692 
   2693         /**
   2694          * Called when a route's volume changes.
   2695          *
   2696          * @param router the MediaRouter reporting the event
   2697          * @param info The route with altered volume
   2698          */
   2699         public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
   2700 
   2701         /**
   2702          * Called when a route's presentation display changes.
   2703          * <p>
   2704          * This method is called whenever the route's presentation display becomes
   2705          * available, is removes or has changes to some of its properties (such as its size).
   2706          * </p>
   2707          *
   2708          * @param router the MediaRouter reporting the event
   2709          * @param info The route whose presentation display changed
   2710          *
   2711          * @see RouteInfo#getPresentationDisplay()
   2712          */
   2713         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
   2714         }
   2715     }
   2716 
   2717     /**
   2718      * Stub implementation of {@link MediaRouter.Callback}.
   2719      * Each abstract method is defined as a no-op. Override just the ones
   2720      * you need.
   2721      */
   2722     public static class SimpleCallback extends Callback {
   2723 
   2724         @Override
   2725         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
   2726         }
   2727 
   2728         @Override
   2729         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
   2730         }
   2731 
   2732         @Override
   2733         public void onRouteAdded(MediaRouter router, RouteInfo info) {
   2734         }
   2735 
   2736         @Override
   2737         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
   2738         }
   2739 
   2740         @Override
   2741         public void onRouteChanged(MediaRouter router, RouteInfo info) {
   2742         }
   2743 
   2744         @Override
   2745         public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
   2746                 int index) {
   2747         }
   2748 
   2749         @Override
   2750         public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
   2751         }
   2752 
   2753         @Override
   2754         public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
   2755         }
   2756     }
   2757 
   2758     static class VolumeCallbackInfo {
   2759         public final VolumeCallback vcb;
   2760         public final RouteInfo route;
   2761 
   2762         public VolumeCallbackInfo(VolumeCallback vcb, RouteInfo route) {
   2763             this.vcb = vcb;
   2764             this.route = route;
   2765         }
   2766     }
   2767 
   2768     /**
   2769      * Interface for receiving events about volume changes.
   2770      * All methods of this interface will be called from the application's main thread.
   2771      *
   2772      * <p>A VolumeCallback will only receive events relevant to routes that the callback
   2773      * was registered for.</p>
   2774      *
   2775      * @see UserRouteInfo#setVolumeCallback(VolumeCallback)
   2776      */
   2777     public static abstract class VolumeCallback {
   2778         /**
   2779          * Called when the volume for the route should be increased or decreased.
   2780          * @param info the route affected by this event
   2781          * @param direction an integer indicating whether the volume is to be increased
   2782          *     (positive value) or decreased (negative value).
   2783          *     For bundled changes, the absolute value indicates the number of changes
   2784          *     in the same direction, e.g. +3 corresponds to three "volume up" changes.
   2785          */
   2786         public abstract void onVolumeUpdateRequest(RouteInfo info, int direction);
   2787         /**
   2788          * Called when the volume for the route should be set to the given value
   2789          * @param info the route affected by this event
   2790          * @param volume an integer indicating the new volume value that should be used, always
   2791          *     between 0 and the value set by {@link UserRouteInfo#setVolumeMax(int)}.
   2792          */
   2793         public abstract void onVolumeSetRequest(RouteInfo info, int volume);
   2794     }
   2795 
   2796     static class VolumeChangeReceiver extends BroadcastReceiver {
   2797         @Override
   2798         public void onReceive(Context context, Intent intent) {
   2799             if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
   2800                 final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
   2801                         -1);
   2802                 if (streamType != AudioManager.STREAM_MUSIC) {
   2803                     return;
   2804                 }
   2805 
   2806                 final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
   2807                 final int oldVolume = intent.getIntExtra(
   2808                         AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
   2809                 if (newVolume != oldVolume) {
   2810                     systemVolumeChanged(newVolume);
   2811                 }
   2812             }
   2813         }
   2814     }
   2815 
   2816     static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
   2817         @Override
   2818         public void onReceive(Context context, Intent intent) {
   2819             if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
   2820                 updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
   2821                         DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
   2822             }
   2823         }
   2824     }
   2825 }
   2826