1 /* 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import android.annotation.SuppressLint; 14 import android.content.BroadcastReceiver; 15 import android.content.Context; 16 import android.content.Intent; 17 import android.content.IntentFilter; 18 import android.net.ConnectivityManager; 19 import android.net.ConnectivityManager.NetworkCallback; 20 import android.net.LinkAddress; 21 import android.net.LinkProperties; 22 import android.net.Network; 23 import android.net.NetworkCapabilities; 24 import android.net.NetworkInfo; 25 import android.net.NetworkRequest; 26 import android.net.wifi.WifiInfo; 27 import android.net.wifi.WifiManager; 28 import android.net.wifi.p2p.WifiP2pGroup; 29 import android.net.wifi.p2p.WifiP2pManager; 30 import android.os.Build; 31 import android.telephony.TelephonyManager; 32 import androidx.annotation.GuardedBy; 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.annotation.VisibleForTesting; 36 import java.net.InetAddress; 37 import java.net.NetworkInterface; 38 import java.net.SocketException; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Set; 44 45 /** 46 * Borrowed from Chromium's 47 * src/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java 48 * 49 * <p>Used by the NetworkMonitor to listen to platform changes in connectivity. Note that use of 50 * this class requires that the app have the platform ACCESS_NETWORK_STATE permission. 51 */ 52 public class NetworkMonitorAutoDetect extends BroadcastReceiver implements NetworkChangeDetector { 53 static class NetworkState { 54 private final boolean connected; 55 // Defined from ConnectivityManager.TYPE_XXX for non-mobile; for mobile, it is 56 // further divided into 2G, 3G, or 4G from the subtype. 57 private final int type; 58 // Defined from NetworkInfo.subtype, which is one of the TelephonyManager.NETWORK_TYPE_XXXs. 59 // Will be useful to find the maximum bandwidth. 60 private final int subtype; 61 // When the type is TYPE_VPN, the following two fields specify the similar type and subtype as 62 // above for the underlying network that is used by the VPN. 63 private final int underlyingNetworkTypeForVpn; 64 private final int underlyingNetworkSubtypeForVpn; 65 NetworkState(boolean connected, int type, int subtype, int underlyingNetworkTypeForVpn, int underlyingNetworkSubtypeForVpn)66 public NetworkState(boolean connected, int type, int subtype, int underlyingNetworkTypeForVpn, 67 int underlyingNetworkSubtypeForVpn) { 68 this.connected = connected; 69 this.type = type; 70 this.subtype = subtype; 71 this.underlyingNetworkTypeForVpn = underlyingNetworkTypeForVpn; 72 this.underlyingNetworkSubtypeForVpn = underlyingNetworkSubtypeForVpn; 73 } 74 isConnected()75 public boolean isConnected() { 76 return connected; 77 } 78 getNetworkType()79 public int getNetworkType() { 80 return type; 81 } 82 getNetworkSubType()83 public int getNetworkSubType() { 84 return subtype; 85 } 86 getUnderlyingNetworkTypeForVpn()87 public int getUnderlyingNetworkTypeForVpn() { 88 return underlyingNetworkTypeForVpn; 89 } 90 getUnderlyingNetworkSubtypeForVpn()91 public int getUnderlyingNetworkSubtypeForVpn() { 92 return underlyingNetworkSubtypeForVpn; 93 } 94 } 95 96 @SuppressLint("NewApi") 97 @VisibleForTesting() 98 class SimpleNetworkCallback extends NetworkCallback { 99 @GuardedBy("availableNetworks") final Set<Network> availableNetworks; 100 SimpleNetworkCallback(Set<Network> availableNetworks)101 SimpleNetworkCallback(Set<Network> availableNetworks) { 102 this.availableNetworks = availableNetworks; 103 } 104 105 @Override onAvailable(Network network)106 public void onAvailable(Network network) { 107 Logging.d(TAG, 108 "Network" 109 + " handle: " + networkToNetId(network) 110 + " becomes available: " + network.toString()); 111 112 synchronized (availableNetworks) { 113 availableNetworks.add(network); 114 } 115 onNetworkChanged(network); 116 } 117 118 @Override onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities)119 public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { 120 // A capabilities change may indicate the ConnectionType has changed, 121 // so forward the new NetworkInformation along to the observer. 122 Logging.d(TAG, 123 "handle: " + networkToNetId(network) 124 + " capabilities changed: " + networkCapabilities.toString()); 125 onNetworkChanged(network); 126 } 127 128 @Override onLinkPropertiesChanged(Network network, LinkProperties linkProperties)129 public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { 130 // A link property change may indicate the IP address changes. 131 // so forward the new NetworkInformation to the observer. 132 // 133 // linkProperties.toString() has PII that cannot be redacted 134 // very reliably, so do not include in log. 135 Logging.d(TAG, "handle: " + networkToNetId(network) + " link properties changed"); 136 onNetworkChanged(network); 137 } 138 139 @Override onLosing(Network network, int maxMsToLive)140 public void onLosing(Network network, int maxMsToLive) { 141 // Tell the network is going to lose in MaxMsToLive milliseconds. 142 // We may use this signal later. 143 Logging.d(TAG, 144 "Network" 145 + " handle: " + networkToNetId(network) + ", " + network.toString() 146 + " is about to lose in " + maxMsToLive + "ms"); 147 } 148 149 @Override onLost(Network network)150 public void onLost(Network network) { 151 Logging.d(TAG, 152 "Network" 153 + " handle: " + networkToNetId(network) + ", " + network.toString() 154 + " is disconnected"); 155 156 synchronized (availableNetworks) { 157 availableNetworks.remove(network); 158 } 159 observer.onNetworkDisconnect(networkToNetId(network)); 160 } 161 onNetworkChanged(Network network)162 private void onNetworkChanged(Network network) { 163 NetworkInformation networkInformation = connectivityManagerDelegate.networkToInfo(network); 164 if (networkInformation != null) { 165 observer.onNetworkConnect(networkInformation); 166 } 167 } 168 } 169 170 /** Queries the ConnectivityManager for information about the current connection. */ 171 static class ConnectivityManagerDelegate { 172 /** 173 * Note: In some rare Android systems connectivityManager is null. We handle that 174 * gracefully below. 175 */ 176 @Nullable private final ConnectivityManager connectivityManager; 177 178 /** 179 * Note: The availableNetworks set is instantiated in NetworkMonitorAutoDetect 180 * and the instance is mutated by SimpleNetworkCallback. 181 */ 182 @NonNull @GuardedBy("availableNetworks") private final Set<Network> availableNetworks; 183 184 /** field trials */ 185 private final boolean getAllNetworksFromCache; 186 private final boolean requestVPN; 187 private final boolean includeOtherUidNetworks; 188 ConnectivityManagerDelegate( Context context, Set<Network> availableNetworks, String fieldTrialsString)189 ConnectivityManagerDelegate( 190 Context context, Set<Network> availableNetworks, String fieldTrialsString) { 191 this((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE), 192 availableNetworks, fieldTrialsString); 193 } 194 195 @VisibleForTesting ConnectivityManagerDelegate(ConnectivityManager connectivityManager, Set<Network> availableNetworks, String fieldTrialsString)196 ConnectivityManagerDelegate(ConnectivityManager connectivityManager, 197 Set<Network> availableNetworks, String fieldTrialsString) { 198 this.connectivityManager = connectivityManager; 199 this.availableNetworks = availableNetworks; 200 this.getAllNetworksFromCache = 201 checkFieldTrial(fieldTrialsString, "getAllNetworksFromCache", false); 202 this.requestVPN = checkFieldTrial(fieldTrialsString, "requestVPN", false); 203 this.includeOtherUidNetworks = 204 checkFieldTrial(fieldTrialsString, "includeOtherUidNetworks", false); 205 } 206 checkFieldTrial( String fieldTrialsString, String key, boolean defaultValue)207 private static boolean checkFieldTrial( 208 String fieldTrialsString, String key, boolean defaultValue) { 209 if (fieldTrialsString.contains(key + ":true")) { 210 return true; 211 } else if (fieldTrialsString.contains(key + ":false")) { 212 return false; 213 } 214 return defaultValue; 215 } 216 217 /** 218 * Returns connection type and status information about the current 219 * default network. 220 */ getNetworkState()221 NetworkState getNetworkState() { 222 if (connectivityManager == null) { 223 return new NetworkState(false, -1, -1, -1, -1); 224 } 225 return getNetworkState(connectivityManager.getActiveNetworkInfo()); 226 } 227 228 /** 229 * Returns connection type and status information about `network`. 230 * Only callable on Lollipop and newer releases. 231 */ 232 @SuppressLint("NewApi") getNetworkState(@ullable Network network)233 NetworkState getNetworkState(@Nullable Network network) { 234 if (network == null || connectivityManager == null) { 235 return new NetworkState(false, -1, -1, -1, -1); 236 } 237 NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); 238 if (networkInfo == null) { 239 Logging.w(TAG, "Couldn't retrieve information from network " + network.toString()); 240 return new NetworkState(false, -1, -1, -1, -1); 241 } 242 // The general logic of handling a VPN in this method is as follows. getNetworkInfo will 243 // return the info of the network with the same id as in `network` when it is registered via 244 // ConnectivityManager.registerNetworkAgent in Android. `networkInfo` may or may not indicate 245 // the type TYPE_VPN if `network` is a VPN. To reliably detect the VPN interface, we need to 246 // query the network capability as below in the case when networkInfo.getType() is not 247 // TYPE_VPN. On the other hand when networkInfo.getType() is TYPE_VPN, the only solution so 248 // far to obtain the underlying network information is to query the active network interface. 249 // However, the active network interface may not be used for the VPN, for example, if the VPN 250 // is restricted to WiFi by the implementation but the WiFi interface is currently turned 251 // off and the active interface is the Cell. Using directly the result from 252 // getActiveNetworkInfo may thus give the wrong interface information, and one should note 253 // that getActiveNetworkInfo would return the default network interface if the VPN does not 254 // specify its underlying networks in the implementation. Therefore, we need further compare 255 // `network` to the active network. If they are not the same network, we will have to fall 256 // back to report an unknown network. 257 258 if (networkInfo.getType() != ConnectivityManager.TYPE_VPN) { 259 // Note that getNetworkCapabilities returns null if the network is unknown. 260 NetworkCapabilities networkCapabilities = 261 connectivityManager.getNetworkCapabilities(network); 262 if (networkCapabilities == null 263 || !networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { 264 return getNetworkState(networkInfo); 265 } 266 // When `network` is in fact a VPN after querying its capability but `networkInfo` is not of 267 // type TYPE_VPN, `networkInfo` contains the info for the underlying network, and we return 268 // a NetworkState constructed from it. 269 return new NetworkState(networkInfo.isConnected(), ConnectivityManager.TYPE_VPN, -1, 270 networkInfo.getType(), networkInfo.getSubtype()); 271 } 272 273 // When `networkInfo` is of type TYPE_VPN, which implies `network` is a VPN, we return the 274 // NetworkState of the active network via getActiveNetworkInfo(), if `network` is the active 275 // network that supports the VPN. Otherwise, NetworkState of an unknown network with type -1 276 // will be returned. 277 // 278 // Note that getActiveNetwork and getActiveNetworkInfo return null if no default network is 279 // currently active. 280 if (networkInfo.getType() == ConnectivityManager.TYPE_VPN) { 281 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M 282 && network.equals(connectivityManager.getActiveNetwork())) { 283 // If a VPN network is in place, we can find the underlying network type via querying the 284 // active network info thanks to 285 // https://android.googlesource.com/platform/frameworks/base/+/d6a7980d 286 NetworkInfo underlyingActiveNetworkInfo = connectivityManager.getActiveNetworkInfo(); 287 // We use the NetworkInfo of the underlying network if it is not of TYPE_VPN itself. 288 if (underlyingActiveNetworkInfo != null 289 && underlyingActiveNetworkInfo.getType() != ConnectivityManager.TYPE_VPN) { 290 return new NetworkState(networkInfo.isConnected(), ConnectivityManager.TYPE_VPN, -1, 291 underlyingActiveNetworkInfo.getType(), underlyingActiveNetworkInfo.getSubtype()); 292 } 293 } 294 return new NetworkState( 295 networkInfo.isConnected(), ConnectivityManager.TYPE_VPN, -1, -1, -1); 296 } 297 298 return getNetworkState(networkInfo); 299 } 300 301 /** 302 * Returns connection type and status information gleaned from networkInfo. Note that to obtain 303 * the complete information about a VPN including the type of the underlying network, one should 304 * use the above method getNetworkState with a Network object. 305 */ getNetworkState(@ullable NetworkInfo networkInfo)306 private NetworkState getNetworkState(@Nullable NetworkInfo networkInfo) { 307 if (networkInfo == null || !networkInfo.isConnected()) { 308 return new NetworkState(false, -1, -1, -1, -1); 309 } 310 return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype(), -1, -1); 311 } 312 313 /** 314 * Returns all connected networks. 315 * Only callable on Lollipop and newer releases. 316 */ 317 @SuppressLint("NewApi") getAllNetworks()318 Network[] getAllNetworks() { 319 if (connectivityManager == null) { 320 return new Network[0]; 321 } 322 323 if (supportNetworkCallback() && getAllNetworksFromCache) { 324 synchronized (availableNetworks) { 325 return availableNetworks.toArray(new Network[0]); 326 } 327 } 328 329 return connectivityManager.getAllNetworks(); 330 } 331 332 @Nullable getActiveNetworkList()333 List<NetworkInformation> getActiveNetworkList() { 334 if (!supportNetworkCallback()) { 335 return null; 336 } 337 ArrayList<NetworkInformation> netInfoList = new ArrayList<NetworkInformation>(); 338 for (Network network : getAllNetworks()) { 339 NetworkInformation info = networkToInfo(network); 340 if (info != null) { 341 netInfoList.add(info); 342 } 343 } 344 return netInfoList; 345 } 346 347 /** 348 * Returns the NetID of the current default network. Returns 349 * INVALID_NET_ID if no current default network connected. 350 * Only callable on Lollipop and newer releases. 351 */ 352 @SuppressLint("NewApi") getDefaultNetId()353 long getDefaultNetId() { 354 if (!supportNetworkCallback()) { 355 return INVALID_NET_ID; 356 } 357 // Android Lollipop had no API to get the default network; only an 358 // API to return the NetworkInfo for the default network. To 359 // determine the default network one can find the network with 360 // type matching that of the default network. 361 final NetworkInfo defaultNetworkInfo = connectivityManager.getActiveNetworkInfo(); 362 if (defaultNetworkInfo == null) { 363 return INVALID_NET_ID; 364 } 365 final Network[] networks = getAllNetworks(); 366 long defaultNetId = INVALID_NET_ID; 367 for (Network network : networks) { 368 if (!hasInternetCapability(network)) { 369 continue; 370 } 371 final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); 372 if (networkInfo != null && networkInfo.getType() == defaultNetworkInfo.getType()) { 373 // There should not be multiple connected networks of the 374 // same type. At least as of Android Marshmallow this is 375 // not supported. If this becomes supported this assertion 376 // may trigger. At that point we could consider using 377 // ConnectivityManager.getDefaultNetwork() though this 378 // may give confusing results with VPNs and is only 379 // available with Android Marshmallow. 380 if (defaultNetId != INVALID_NET_ID) { 381 throw new RuntimeException( 382 "Multiple connected networks of same type are not supported."); 383 } 384 defaultNetId = networkToNetId(network); 385 } 386 } 387 return defaultNetId; 388 } 389 390 @SuppressLint("NewApi") networkToInfo(@ullable Network network)391 private @Nullable NetworkInformation networkToInfo(@Nullable Network network) { 392 if (network == null || connectivityManager == null) { 393 return null; 394 } 395 LinkProperties linkProperties = connectivityManager.getLinkProperties(network); 396 // getLinkProperties will return null if the network is unknown. 397 if (linkProperties == null) { 398 Logging.w(TAG, "Detected unknown network: " + network.toString()); 399 return null; 400 } 401 if (linkProperties.getInterfaceName() == null) { 402 Logging.w(TAG, "Null interface name for network " + network.toString()); 403 return null; 404 } 405 406 NetworkState networkState = getNetworkState(network); 407 NetworkChangeDetector.ConnectionType connectionType = getConnectionType(networkState); 408 if (connectionType == NetworkChangeDetector.ConnectionType.CONNECTION_NONE) { 409 // This may not be an error. The OS may signal a network event with connection type 410 // NONE when the network disconnects. 411 Logging.d(TAG, "Network " + network.toString() + " is disconnected"); 412 return null; 413 } 414 415 // Some android device may return a CONNECTION_UNKNOWN_CELLULAR or CONNECTION_UNKNOWN type, 416 // which appears to be usable. Just log them here. 417 if (connectionType == NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN 418 || connectionType == NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN_CELLULAR) { 419 Logging.d(TAG, "Network " + network.toString() + " connection type is " + connectionType 420 + " because it has type " + networkState.getNetworkType() + " and subtype " 421 + networkState.getNetworkSubType()); 422 } 423 // NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN if the network is not a VPN or the 424 // underlying network is 425 // unknown. 426 ConnectionType underlyingConnectionTypeForVpn = 427 getUnderlyingConnectionTypeForVpn(networkState); 428 429 NetworkInformation networkInformation = new NetworkInformation( 430 linkProperties.getInterfaceName(), connectionType, underlyingConnectionTypeForVpn, 431 networkToNetId(network), getIPAddresses(linkProperties)); 432 return networkInformation; 433 } 434 435 /** 436 * Returns true if {@code network} can provide Internet access. Can be used to 437 * ignore specialized networks (e.g. IMS, FOTA). 438 */ 439 @SuppressLint("NewApi") hasInternetCapability(Network network)440 boolean hasInternetCapability(Network network) { 441 if (connectivityManager == null) { 442 return false; 443 } 444 final NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); 445 return capabilities != null 446 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 447 } 448 449 @SuppressLint("NewApi") 450 @VisibleForTesting() createNetworkRequest()451 NetworkRequest createNetworkRequest() { 452 // Requests the following capabilities by default: NOT_VPN, NOT_RESTRICTED, TRUSTED 453 NetworkRequest.Builder builder = 454 new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 455 456 if (requestVPN) { 457 builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); 458 } 459 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && includeOtherUidNetworks) { 460 builder.setIncludeOtherUidNetworks(true); 461 } 462 return builder.build(); 463 } 464 465 /** Only callable on Lollipop and newer releases. */ 466 @SuppressLint("NewApi") registerNetworkCallback(NetworkCallback networkCallback)467 public void registerNetworkCallback(NetworkCallback networkCallback) { 468 connectivityManager.registerNetworkCallback(createNetworkRequest(), networkCallback); 469 } 470 471 /** Only callable on Lollipop and newer releases. */ 472 @SuppressLint("NewApi") requestMobileNetwork(NetworkCallback networkCallback)473 public void requestMobileNetwork(NetworkCallback networkCallback) { 474 NetworkRequest.Builder builder = new NetworkRequest.Builder(); 475 builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 476 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); 477 connectivityManager.requestNetwork(builder.build(), networkCallback); 478 } 479 480 @SuppressLint("NewApi") getIPAddresses(LinkProperties linkProperties)481 IPAddress[] getIPAddresses(LinkProperties linkProperties) { 482 IPAddress[] ipAddresses = new IPAddress[linkProperties.getLinkAddresses().size()]; 483 int i = 0; 484 for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { 485 ipAddresses[i] = new IPAddress(linkAddress.getAddress().getAddress()); 486 ++i; 487 } 488 return ipAddresses; 489 } 490 491 @SuppressLint("NewApi") releaseCallback(NetworkCallback networkCallback)492 public void releaseCallback(NetworkCallback networkCallback) { 493 if (supportNetworkCallback()) { 494 Logging.d(TAG, "Unregister network callback"); 495 connectivityManager.unregisterNetworkCallback(networkCallback); 496 } 497 } 498 supportNetworkCallback()499 public boolean supportNetworkCallback() { 500 return connectivityManager != null; 501 } 502 } 503 504 /** Queries the WifiManager for SSID of the current Wifi connection. */ 505 static class WifiManagerDelegate { 506 @Nullable private final Context context; WifiManagerDelegate(Context context)507 WifiManagerDelegate(Context context) { 508 this.context = context; 509 } 510 511 // For testing. WifiManagerDelegate()512 WifiManagerDelegate() { 513 // All the methods below should be overridden. 514 context = null; 515 } 516 getWifiSSID()517 String getWifiSSID() { 518 final Intent intent = context.registerReceiver( 519 null, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); 520 if (intent != null) { 521 final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); 522 if (wifiInfo != null) { 523 final String ssid = wifiInfo.getSSID(); 524 if (ssid != null) { 525 return ssid; 526 } 527 } 528 } 529 return ""; 530 } 531 } 532 533 /** Maintains the information about wifi direct (aka WifiP2p) networks. */ 534 static class WifiDirectManagerDelegate extends BroadcastReceiver { 535 // Network "handle" for the Wifi P2p network. We have to bind to the default network id 536 // (NETWORK_UNSPECIFIED) for these addresses. 537 private static final int WIFI_P2P_NETWORK_HANDLE = 0; 538 private final Context context; 539 private final NetworkChangeDetector.Observer observer; 540 // Network information about a WifiP2p (aka WiFi-Direct) network, or null if no such network is 541 // connected. 542 @Nullable private NetworkInformation wifiP2pNetworkInfo; 543 WifiDirectManagerDelegate(NetworkChangeDetector.Observer observer, Context context)544 WifiDirectManagerDelegate(NetworkChangeDetector.Observer observer, Context context) { 545 this.context = context; 546 this.observer = observer; 547 IntentFilter intentFilter = new IntentFilter(); 548 intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 549 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 550 context.registerReceiver(this, intentFilter); 551 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { 552 // Starting with Android Q (10), WIFI_P2P_CONNECTION_CHANGED_ACTION is no longer sticky. 553 // This means we have to explicitly request WifiP2pGroup info during initialization in order 554 // to get this data if we are already connected to a Wi-Fi Direct network. 555 WifiP2pManager manager = 556 (WifiP2pManager) context.getSystemService(Context.WIFI_P2P_SERVICE); 557 WifiP2pManager.Channel channel = 558 manager.initialize(context, context.getMainLooper(), null /* listener */); 559 manager.requestGroupInfo(channel, wifiP2pGroup -> { onWifiP2pGroupChange(wifiP2pGroup); }); 560 } 561 } 562 563 // BroadcastReceiver 564 @Override 565 @SuppressLint("InlinedApi") onReceive(Context context, Intent intent)566 public void onReceive(Context context, Intent intent) { 567 if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(intent.getAction())) { 568 WifiP2pGroup wifiP2pGroup = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); 569 onWifiP2pGroupChange(wifiP2pGroup); 570 } else if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(intent.getAction())) { 571 int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0 /* default to unknown */); 572 onWifiP2pStateChange(state); 573 } 574 } 575 576 /** Releases the broadcast receiver. */ release()577 public void release() { 578 context.unregisterReceiver(this); 579 } 580 getActiveNetworkList()581 public List<NetworkInformation> getActiveNetworkList() { 582 if (wifiP2pNetworkInfo != null) { 583 return Collections.singletonList(wifiP2pNetworkInfo); 584 } 585 586 return Collections.emptyList(); 587 } 588 589 /** Handle a change notification about the wifi p2p group. */ onWifiP2pGroupChange(@ullable WifiP2pGroup wifiP2pGroup)590 private void onWifiP2pGroupChange(@Nullable WifiP2pGroup wifiP2pGroup) { 591 if (wifiP2pGroup == null || wifiP2pGroup.getInterface() == null) { 592 return; 593 } 594 595 NetworkInterface wifiP2pInterface; 596 try { 597 wifiP2pInterface = NetworkInterface.getByName(wifiP2pGroup.getInterface()); 598 } catch (SocketException e) { 599 Logging.e(TAG, "Unable to get WifiP2p network interface", e); 600 return; 601 } 602 603 List<InetAddress> interfaceAddresses = Collections.list(wifiP2pInterface.getInetAddresses()); 604 IPAddress[] ipAddresses = new IPAddress[interfaceAddresses.size()]; 605 for (int i = 0; i < interfaceAddresses.size(); ++i) { 606 ipAddresses[i] = new IPAddress(interfaceAddresses.get(i).getAddress()); 607 } 608 609 wifiP2pNetworkInfo = new NetworkInformation(wifiP2pGroup.getInterface(), 610 NetworkChangeDetector.ConnectionType.CONNECTION_WIFI, 611 NetworkChangeDetector.ConnectionType.CONNECTION_NONE, WIFI_P2P_NETWORK_HANDLE, 612 ipAddresses); 613 observer.onNetworkConnect(wifiP2pNetworkInfo); 614 } 615 616 /** Handle a state change notification about wifi p2p. */ onWifiP2pStateChange(int state)617 private void onWifiP2pStateChange(int state) { 618 if (state == WifiP2pManager.WIFI_P2P_STATE_DISABLED) { 619 wifiP2pNetworkInfo = null; 620 observer.onNetworkDisconnect(WIFI_P2P_NETWORK_HANDLE); 621 } 622 } 623 } 624 625 private static final long INVALID_NET_ID = -1; 626 private static final String TAG = "NetworkMonitorAutoDetect"; 627 628 // Observer for the connection type change. 629 private final NetworkChangeDetector.Observer observer; 630 private final IntentFilter intentFilter; 631 private final Context context; 632 // Used to request mobile network. It does not do anything except for keeping 633 // the callback for releasing the request. 634 @Nullable private final NetworkCallback mobileNetworkCallback; 635 // Used to receive updates on all networks. 636 @Nullable private final NetworkCallback allNetworkCallback; 637 // connectivityManagerDelegate and wifiManagerDelegate are only non-final for testing. 638 private ConnectivityManagerDelegate connectivityManagerDelegate; 639 private WifiManagerDelegate wifiManagerDelegate; 640 private WifiDirectManagerDelegate wifiDirectManagerDelegate; 641 private static boolean includeWifiDirect; 642 643 @GuardedBy("availableNetworks") final Set<Network> availableNetworks = new HashSet<>(); 644 645 private boolean isRegistered; 646 private NetworkChangeDetector.ConnectionType connectionType; 647 private String wifiSSID; 648 649 /** Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread. */ 650 @SuppressLint("NewApi") NetworkMonitorAutoDetect(NetworkChangeDetector.Observer observer, Context context)651 public NetworkMonitorAutoDetect(NetworkChangeDetector.Observer observer, Context context) { 652 this.observer = observer; 653 this.context = context; 654 String fieldTrialsString = observer.getFieldTrialsString(); 655 connectivityManagerDelegate = 656 new ConnectivityManagerDelegate(context, availableNetworks, fieldTrialsString); 657 wifiManagerDelegate = new WifiManagerDelegate(context); 658 659 final NetworkState networkState = connectivityManagerDelegate.getNetworkState(); 660 connectionType = getConnectionType(networkState); 661 wifiSSID = getWifiSSID(networkState); 662 intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 663 664 if (includeWifiDirect) { 665 wifiDirectManagerDelegate = new WifiDirectManagerDelegate(observer, context); 666 } 667 668 registerReceiver(); 669 if (connectivityManagerDelegate.supportNetworkCallback()) { 670 // On Android 6.0.0, the WRITE_SETTINGS permission is necessary for 671 // requestNetwork, so it will fail. This was fixed in Android 6.0.1. 672 NetworkCallback tempNetworkCallback = new NetworkCallback(); 673 try { 674 connectivityManagerDelegate.requestMobileNetwork(tempNetworkCallback); 675 } catch (java.lang.SecurityException e) { 676 Logging.w(TAG, "Unable to obtain permission to request a cellular network."); 677 tempNetworkCallback = null; 678 } 679 mobileNetworkCallback = tempNetworkCallback; 680 allNetworkCallback = new SimpleNetworkCallback(availableNetworks); 681 connectivityManagerDelegate.registerNetworkCallback(allNetworkCallback); 682 } else { 683 mobileNetworkCallback = null; 684 allNetworkCallback = null; 685 } 686 } 687 688 /** Enables WifiDirectManager. */ setIncludeWifiDirect(boolean enable)689 public static void setIncludeWifiDirect(boolean enable) { 690 includeWifiDirect = enable; 691 } 692 693 @Override supportNetworkCallback()694 public boolean supportNetworkCallback() { 695 return connectivityManagerDelegate.supportNetworkCallback(); 696 } 697 698 /** 699 * Allows overriding the ConnectivityManagerDelegate for tests. 700 */ setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate)701 void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) { 702 connectivityManagerDelegate = delegate; 703 } 704 705 /** 706 * Allows overriding the WifiManagerDelegate for tests. 707 */ setWifiManagerDelegateForTests(WifiManagerDelegate delegate)708 void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) { 709 wifiManagerDelegate = delegate; 710 } 711 712 /** 713 * Returns whether the object has registered to receive network connectivity intents. 714 * Visible for testing. 715 */ isReceiverRegisteredForTesting()716 boolean isReceiverRegisteredForTesting() { 717 return isRegistered; 718 } 719 720 @Override 721 @Nullable getActiveNetworkList()722 public List<NetworkInformation> getActiveNetworkList() { 723 List<NetworkInformation> connectivityManagerList = 724 connectivityManagerDelegate.getActiveNetworkList(); 725 if (connectivityManagerList == null) { 726 return null; 727 } 728 ArrayList<NetworkInformation> result = 729 new ArrayList<NetworkInformation>(connectivityManagerList); 730 if (wifiDirectManagerDelegate != null) { 731 result.addAll(wifiDirectManagerDelegate.getActiveNetworkList()); 732 } 733 return result; 734 } 735 736 @Override destroy()737 public void destroy() { 738 if (allNetworkCallback != null) { 739 connectivityManagerDelegate.releaseCallback(allNetworkCallback); 740 } 741 if (mobileNetworkCallback != null) { 742 connectivityManagerDelegate.releaseCallback(mobileNetworkCallback); 743 } 744 if (wifiDirectManagerDelegate != null) { 745 wifiDirectManagerDelegate.release(); 746 } 747 unregisterReceiver(); 748 } 749 750 /** 751 * Registers a BroadcastReceiver in the given context. 752 */ registerReceiver()753 private void registerReceiver() { 754 if (isRegistered) 755 return; 756 757 isRegistered = true; 758 context.registerReceiver(this, intentFilter); 759 } 760 761 /** 762 * Unregisters the BroadcastReceiver in the given context. 763 */ unregisterReceiver()764 private void unregisterReceiver() { 765 if (!isRegistered) 766 return; 767 768 isRegistered = false; 769 context.unregisterReceiver(this); 770 } 771 getCurrentNetworkState()772 public NetworkState getCurrentNetworkState() { 773 return connectivityManagerDelegate.getNetworkState(); 774 } 775 776 /** 777 * Returns NetID of device's current default connected network used for 778 * communication. 779 * Only implemented on Lollipop and newer releases, returns INVALID_NET_ID 780 * when not implemented. 781 */ getDefaultNetId()782 public long getDefaultNetId() { 783 return connectivityManagerDelegate.getDefaultNetId(); 784 } 785 getConnectionType( boolean isConnected, int networkType, int networkSubtype)786 private static NetworkChangeDetector.ConnectionType getConnectionType( 787 boolean isConnected, int networkType, int networkSubtype) { 788 if (!isConnected) { 789 return NetworkChangeDetector.ConnectionType.CONNECTION_NONE; 790 } 791 792 switch (networkType) { 793 case ConnectivityManager.TYPE_ETHERNET: 794 return NetworkChangeDetector.ConnectionType.CONNECTION_ETHERNET; 795 case ConnectivityManager.TYPE_WIFI: 796 return NetworkChangeDetector.ConnectionType.CONNECTION_WIFI; 797 case ConnectivityManager.TYPE_WIMAX: 798 return NetworkChangeDetector.ConnectionType.CONNECTION_4G; 799 case ConnectivityManager.TYPE_BLUETOOTH: 800 return NetworkChangeDetector.ConnectionType.CONNECTION_BLUETOOTH; 801 case ConnectivityManager.TYPE_MOBILE: 802 case ConnectivityManager.TYPE_MOBILE_DUN: 803 case ConnectivityManager.TYPE_MOBILE_HIPRI: 804 // Use information from TelephonyManager to classify the connection. 805 switch (networkSubtype) { 806 case TelephonyManager.NETWORK_TYPE_GPRS: 807 case TelephonyManager.NETWORK_TYPE_EDGE: 808 case TelephonyManager.NETWORK_TYPE_CDMA: 809 case TelephonyManager.NETWORK_TYPE_1xRTT: 810 case TelephonyManager.NETWORK_TYPE_IDEN: 811 case TelephonyManager.NETWORK_TYPE_GSM: 812 return NetworkChangeDetector.ConnectionType.CONNECTION_2G; 813 case TelephonyManager.NETWORK_TYPE_UMTS: 814 case TelephonyManager.NETWORK_TYPE_EVDO_0: 815 case TelephonyManager.NETWORK_TYPE_EVDO_A: 816 case TelephonyManager.NETWORK_TYPE_HSDPA: 817 case TelephonyManager.NETWORK_TYPE_HSUPA: 818 case TelephonyManager.NETWORK_TYPE_HSPA: 819 case TelephonyManager.NETWORK_TYPE_EVDO_B: 820 case TelephonyManager.NETWORK_TYPE_EHRPD: 821 case TelephonyManager.NETWORK_TYPE_HSPAP: 822 case TelephonyManager.NETWORK_TYPE_TD_SCDMA: 823 return NetworkChangeDetector.ConnectionType.CONNECTION_3G; 824 case TelephonyManager.NETWORK_TYPE_LTE: 825 case TelephonyManager.NETWORK_TYPE_IWLAN: 826 return NetworkChangeDetector.ConnectionType.CONNECTION_4G; 827 case TelephonyManager.NETWORK_TYPE_NR: 828 return NetworkChangeDetector.ConnectionType.CONNECTION_5G; 829 default: 830 return NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN_CELLULAR; 831 } 832 case ConnectivityManager.TYPE_VPN: 833 return NetworkChangeDetector.ConnectionType.CONNECTION_VPN; 834 default: 835 return NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN; 836 } 837 } 838 getConnectionType(NetworkState networkState)839 public static NetworkChangeDetector.ConnectionType getConnectionType(NetworkState networkState) { 840 return getConnectionType(networkState.isConnected(), networkState.getNetworkType(), 841 networkState.getNetworkSubType()); 842 } 843 844 @Override getCurrentConnectionType()845 public NetworkChangeDetector.ConnectionType getCurrentConnectionType() { 846 return getConnectionType(getCurrentNetworkState()); 847 } 848 getUnderlyingConnectionTypeForVpn( NetworkState networkState)849 private static NetworkChangeDetector.ConnectionType getUnderlyingConnectionTypeForVpn( 850 NetworkState networkState) { 851 if (networkState.getNetworkType() != ConnectivityManager.TYPE_VPN) { 852 return NetworkChangeDetector.ConnectionType.CONNECTION_NONE; 853 } 854 return getConnectionType(networkState.isConnected(), 855 networkState.getUnderlyingNetworkTypeForVpn(), 856 networkState.getUnderlyingNetworkSubtypeForVpn()); 857 } 858 getWifiSSID(NetworkState networkState)859 private String getWifiSSID(NetworkState networkState) { 860 if (getConnectionType(networkState) != NetworkChangeDetector.ConnectionType.CONNECTION_WIFI) 861 return ""; 862 return wifiManagerDelegate.getWifiSSID(); 863 } 864 865 // BroadcastReceiver 866 @Override onReceive(Context context, Intent intent)867 public void onReceive(Context context, Intent intent) { 868 final NetworkState networkState = getCurrentNetworkState(); 869 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { 870 connectionTypeChanged(networkState); 871 } 872 } 873 connectionTypeChanged(NetworkState networkState)874 private void connectionTypeChanged(NetworkState networkState) { 875 NetworkChangeDetector.ConnectionType newConnectionType = getConnectionType(networkState); 876 String newWifiSSID = getWifiSSID(networkState); 877 if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) 878 return; 879 880 connectionType = newConnectionType; 881 wifiSSID = newWifiSSID; 882 Logging.d(TAG, "Network connectivity changed, type is: " + connectionType); 883 observer.onConnectionTypeChanged(newConnectionType); 884 } 885 886 /** 887 * Extracts NetID of network on Lollipop and NetworkHandle (which is mungled 888 * NetID) on Marshmallow and newer releases. Only available on Lollipop and 889 * newer releases. Returns long since getNetworkHandle returns long. 890 */ 891 @SuppressLint("NewApi") networkToNetId(Network network)892 private static long networkToNetId(Network network) { 893 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 894 return network.getNetworkHandle(); 895 } 896 897 // NOTE(honghaiz): This depends on Android framework implementation details. 898 // These details cannot change because Lollipop has been released. 899 return Integer.parseInt(network.toString()); 900 } 901 } 902