1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import static android.net.ConnectivityManager.TYPE_VPN; 8 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 9 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; 10 import static android.net.NetworkCapabilities.TRANSPORT_VPN; 11 12 import android.Manifest.permission; 13 import android.annotation.SuppressLint; 14 import android.annotation.TargetApi; 15 import android.content.BroadcastReceiver; 16 import android.content.Context; 17 import android.content.Intent; 18 import android.content.IntentFilter; 19 import android.content.pm.PackageManager; 20 import android.net.ConnectivityManager; 21 import android.net.ConnectivityManager.NetworkCallback; 22 import android.net.LinkProperties; 23 import android.net.Network; 24 import android.net.NetworkCapabilities; 25 import android.net.NetworkInfo; 26 import android.net.NetworkRequest; 27 import android.net.wifi.WifiInfo; 28 import android.net.wifi.WifiManager; 29 import android.os.Build; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.telephony.TelephonyManager; 33 34 import androidx.annotation.VisibleForTesting; 35 36 import org.chromium.base.ApplicationState; 37 import org.chromium.base.ApplicationStatus; 38 import org.chromium.base.BuildConfig; 39 import org.chromium.base.ContextUtils; 40 import org.chromium.base.StrictModeContext; 41 import org.chromium.base.compat.ApiHelperForM; 42 43 import java.io.IOException; 44 import java.net.Socket; 45 import java.util.Arrays; 46 47 import javax.annotation.concurrent.GuardedBy; 48 49 /** 50 * Used by the NetworkChangeNotifier to listens to platform changes in connectivity. 51 * Note that use of this class requires that the app have the platform 52 * ACCESS_NETWORK_STATE permission. 53 */ 54 // TODO(crbug.com/635567): Fix this properly. 55 @SuppressLint("NewApi") 56 public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver { 57 /** 58 * Immutable class representing the state of a device's network. 59 */ 60 public static class NetworkState { 61 private final boolean mConnected; 62 private final int mType; 63 private final int mSubtype; 64 // WIFI SSID of the connection on pre-Marshmallow, NetID starting with Marshmallow. Always 65 // non-null (i.e. instead of null it'll be an empty string) to facilitate .equals(). 66 private final String mNetworkIdentifier; 67 // Indicates if this network is using DNS-over-TLS. 68 private final boolean mIsPrivateDnsActive; 69 // Indicates the DNS-over-TLS server in use, if specified. 70 private final String mPrivateDnsServerName; 71 NetworkState(boolean connected, int type, int subtype, String networkIdentifier, boolean isPrivateDnsActive, String privateDnsServerName)72 public NetworkState(boolean connected, int type, int subtype, String networkIdentifier, 73 boolean isPrivateDnsActive, String privateDnsServerName) { 74 mConnected = connected; 75 mType = type; 76 mSubtype = subtype; 77 mNetworkIdentifier = networkIdentifier == null ? "" : networkIdentifier; 78 mIsPrivateDnsActive = isPrivateDnsActive; 79 mPrivateDnsServerName = privateDnsServerName == null ? "" : privateDnsServerName; 80 } 81 isConnected()82 public boolean isConnected() { 83 return mConnected; 84 } 85 getNetworkType()86 public int getNetworkType() { 87 return mType; 88 } 89 getNetworkSubType()90 public int getNetworkSubType() { 91 return mSubtype; 92 } 93 94 // Always non-null to facilitate .equals(). getNetworkIdentifier()95 public String getNetworkIdentifier() { 96 return mNetworkIdentifier; 97 } 98 99 /** 100 * Returns the connection type for the given NetworkState. 101 */ 102 @ConnectionType getConnectionType()103 public int getConnectionType() { 104 if (!isConnected()) { 105 return ConnectionType.CONNECTION_NONE; 106 } 107 return convertToConnectionType(getNetworkType(), getNetworkSubType()); 108 } 109 110 /** 111 * Returns the connection subtype for the given NetworkState. 112 */ getConnectionSubtype()113 public int getConnectionSubtype() { 114 if (!isConnected()) { 115 return ConnectionSubtype.SUBTYPE_NONE; 116 } 117 118 switch (getNetworkType()) { 119 case ConnectivityManager.TYPE_ETHERNET: 120 case ConnectivityManager.TYPE_WIFI: 121 case ConnectivityManager.TYPE_WIMAX: 122 case ConnectivityManager.TYPE_BLUETOOTH: 123 return ConnectionSubtype.SUBTYPE_UNKNOWN; 124 case ConnectivityManager.TYPE_MOBILE: 125 case ConnectivityManager.TYPE_MOBILE_DUN: 126 case ConnectivityManager.TYPE_MOBILE_HIPRI: 127 // Use information from TelephonyManager to classify the connection. 128 switch (getNetworkSubType()) { 129 case TelephonyManager.NETWORK_TYPE_GPRS: 130 return ConnectionSubtype.SUBTYPE_GPRS; 131 case TelephonyManager.NETWORK_TYPE_EDGE: 132 return ConnectionSubtype.SUBTYPE_EDGE; 133 case TelephonyManager.NETWORK_TYPE_CDMA: 134 return ConnectionSubtype.SUBTYPE_CDMA; 135 case TelephonyManager.NETWORK_TYPE_1xRTT: 136 return ConnectionSubtype.SUBTYPE_1XRTT; 137 case TelephonyManager.NETWORK_TYPE_IDEN: 138 return ConnectionSubtype.SUBTYPE_IDEN; 139 case TelephonyManager.NETWORK_TYPE_UMTS: 140 return ConnectionSubtype.SUBTYPE_UMTS; 141 case TelephonyManager.NETWORK_TYPE_EVDO_0: 142 return ConnectionSubtype.SUBTYPE_EVDO_REV_0; 143 case TelephonyManager.NETWORK_TYPE_EVDO_A: 144 return ConnectionSubtype.SUBTYPE_EVDO_REV_A; 145 case TelephonyManager.NETWORK_TYPE_HSDPA: 146 return ConnectionSubtype.SUBTYPE_HSDPA; 147 case TelephonyManager.NETWORK_TYPE_HSUPA: 148 return ConnectionSubtype.SUBTYPE_HSUPA; 149 case TelephonyManager.NETWORK_TYPE_HSPA: 150 return ConnectionSubtype.SUBTYPE_HSPA; 151 case TelephonyManager.NETWORK_TYPE_EVDO_B: 152 return ConnectionSubtype.SUBTYPE_EVDO_REV_B; 153 case TelephonyManager.NETWORK_TYPE_EHRPD: 154 return ConnectionSubtype.SUBTYPE_EHRPD; 155 case TelephonyManager.NETWORK_TYPE_HSPAP: 156 return ConnectionSubtype.SUBTYPE_HSPAP; 157 case TelephonyManager.NETWORK_TYPE_LTE: 158 return ConnectionSubtype.SUBTYPE_LTE; 159 default: 160 return ConnectionSubtype.SUBTYPE_UNKNOWN; 161 } 162 default: 163 return ConnectionSubtype.SUBTYPE_UNKNOWN; 164 } 165 } 166 167 /** 168 * Returns boolean indicating if this network uses DNS-over-TLS. 169 */ isPrivateDnsActive()170 public boolean isPrivateDnsActive() { 171 return mIsPrivateDnsActive; 172 } 173 174 /** 175 * Returns the DNS-over-TLS server in use, if specified. 176 */ getPrivateDnsServerName()177 public String getPrivateDnsServerName() { 178 return mPrivateDnsServerName; 179 } 180 } 181 182 /** Queries the ConnectivityManager for information about the current connection. */ 183 static class ConnectivityManagerDelegate { 184 private final ConnectivityManager mConnectivityManager; 185 ConnectivityManagerDelegate(Context context)186 ConnectivityManagerDelegate(Context context) { 187 mConnectivityManager = 188 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 189 } 190 191 // For testing. ConnectivityManagerDelegate()192 ConnectivityManagerDelegate() { 193 // All the methods below should be overridden. 194 mConnectivityManager = null; 195 } 196 197 /** 198 * @param networkInfo The NetworkInfo for the active network. 199 * @return the info of the network that is available to this app. 200 */ 201 @TargetApi(Build.VERSION_CODES.LOLLIPOP) processActiveNetworkInfo(NetworkInfo networkInfo)202 private NetworkInfo processActiveNetworkInfo(NetworkInfo networkInfo) { 203 if (networkInfo == null) { 204 return null; 205 } 206 207 if (networkInfo.isConnected()) { 208 return networkInfo; 209 } 210 211 // If |networkInfo| is BLOCKED, but the app is in the foreground, then it's likely that 212 // Android hasn't finished updating the network access permissions as BLOCKED is only 213 // meant for apps in the background. See https://crbug.com/677365 for more details. 214 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 215 // https://crbug.com/677365 primarily affects only Lollipop and higher versions. 216 return null; 217 } 218 219 if (networkInfo.getDetailedState() != NetworkInfo.DetailedState.BLOCKED) { 220 // Network state is not blocked which implies that network access is 221 // unavailable (not just blocked to this app). 222 return null; 223 } 224 225 if (ApplicationStatus.getStateForApplication() 226 != ApplicationState.HAS_RUNNING_ACTIVITIES) { 227 // The app is not in the foreground. 228 return null; 229 } 230 return networkInfo; 231 } 232 233 /** 234 * Returns connection type and status information about the current 235 * default network. 236 */ getNetworkState(WifiManagerDelegate wifiManagerDelegate)237 NetworkState getNetworkState(WifiManagerDelegate wifiManagerDelegate) { 238 Network network = null; 239 NetworkInfo networkInfo; 240 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 241 network = getDefaultNetwork(); 242 networkInfo = ApiHelperForM.getNetworkInfo(mConnectivityManager, network); 243 } else { 244 networkInfo = mConnectivityManager.getActiveNetworkInfo(); 245 } 246 networkInfo = processActiveNetworkInfo(networkInfo); 247 if (networkInfo == null) { 248 return new NetworkState(false, -1, -1, null, false, ""); 249 } 250 if (network != null) { 251 DnsStatus dnsStatus = AndroidNetworkLibrary.getDnsStatus(network); 252 if (dnsStatus == null) { 253 return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype(), 254 String.valueOf(networkToNetId(network)), false, ""); 255 } else { 256 return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype(), 257 String.valueOf(networkToNetId(network)), 258 dnsStatus.getPrivateDnsActive(), dnsStatus.getPrivateDnsServerName()); 259 } 260 } 261 assert Build.VERSION.SDK_INT < Build.VERSION_CODES.M; 262 // If Wifi, then fetch SSID also 263 if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { 264 // Since Android 4.2 the SSID can be retrieved from NetworkInfo.getExtraInfo(). 265 if (networkInfo.getExtraInfo() != null && !"".equals(networkInfo.getExtraInfo())) { 266 return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype(), 267 networkInfo.getExtraInfo(), false, ""); 268 } 269 // Fetch WiFi SSID directly from WifiManagerDelegate if not in NetworkInfo. 270 return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype(), 271 wifiManagerDelegate.getWifiSsid(), false, ""); 272 } 273 return new NetworkState( 274 true, networkInfo.getType(), networkInfo.getSubtype(), null, false, ""); 275 } 276 277 // Fetches NetworkInfo and records UMA for NullPointerExceptions. 278 private NetworkInfo getNetworkInfo(Network network) { 279 try { 280 return mConnectivityManager.getNetworkInfo(network); 281 } catch (NullPointerException firstException) { 282 // Rarely this unexpectedly throws. Retry or just return {@code null} if it fails. 283 try { 284 return mConnectivityManager.getNetworkInfo(network); 285 } catch (NullPointerException secondException) { 286 return null; 287 } 288 } 289 } 290 291 /** 292 * Returns connection type for |network|. 293 * Only callable on Lollipop and newer releases. 294 */ 295 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 296 @ConnectionType 297 int getConnectionType(Network network) { 298 NetworkInfo networkInfo = getNetworkInfo(network); 299 if (networkInfo != null && networkInfo.getType() == TYPE_VPN) { 300 // When a VPN is in place the underlying network type can be queried via 301 // getActiveNeworkInfo() thanks to 302 // https://android.googlesource.com/platform/frameworks/base/+/d6a7980d 303 networkInfo = mConnectivityManager.getActiveNetworkInfo(); 304 } 305 if (networkInfo != null && networkInfo.isConnected()) { 306 return convertToConnectionType(networkInfo.getType(), networkInfo.getSubtype()); 307 } 308 return ConnectionType.CONNECTION_NONE; 309 } 310 311 /** 312 * Returns all connected networks. This may include networks that aren't useful 313 * to Chrome (e.g. MMS, IMS, FOTA etc) or aren't accessible to Chrome (e.g. a VPN for 314 * another user); use {@link getAllNetworks} for a filtered list. 315 * Only callable on Lollipop and newer releases. 316 */ 317 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 318 @VisibleForTesting 319 protected Network[] getAllNetworksUnfiltered() { 320 Network[] networks = mConnectivityManager.getAllNetworks(); 321 // Very rarely this API inexplicably returns {@code null}, crbug.com/721116. 322 return networks == null ? new Network[0] : networks; 323 } 324 325 /** 326 * Returns {@code true} if {@code network} applies to (and hence is accessible) to the 327 * current user. 328 */ 329 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 330 @VisibleForTesting 331 protected boolean vpnAccessible(Network network) { 332 // Determine if the VPN applies to the current user by seeing if a socket can be bound 333 // to the VPN. 334 Socket s = new Socket(); 335 // Disable detectUntaggedSockets StrictMode policy to avoid false positives, as |s| 336 // isn't used to send or receive traffic. https://crbug.com/946531 337 try (StrictModeContext ignored = StrictModeContext.allowAllVmPolicies()) { 338 // Avoid using network.getSocketFactory().createSocket() because it leaks. 339 // https://crbug.com/805424 340 network.bindSocket(s); 341 } catch (IOException e) { 342 // Failed to bind so this VPN isn't for the current user to use. 343 return false; 344 } finally { 345 try { 346 s.close(); 347 } catch (IOException e) { 348 // Not worth taking action on a failed close. 349 } 350 } 351 return true; 352 } 353 354 /** 355 * Return the NetworkCapabilities for {@code network}, or {@code null} if they cannot 356 * be retrieved (e.g. {@code network} has disconnected). 357 */ 358 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 359 @VisibleForTesting 360 protected NetworkCapabilities getNetworkCapabilities(Network network) { 361 return mConnectivityManager.getNetworkCapabilities(network); 362 } 363 364 /** 365 * Registers networkCallback to receive notifications about networks 366 * that satisfy networkRequest. 367 * Only callable on Lollipop and newer releases. 368 */ 369 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 370 void registerNetworkCallback( 371 NetworkRequest networkRequest, NetworkCallback networkCallback, Handler handler) { 372 // Starting with Oreo specifying a Handler is allowed. Use this to avoid thread-hops. 373 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 374 mConnectivityManager.registerNetworkCallback( 375 networkRequest, networkCallback, handler); 376 } else { 377 mConnectivityManager.registerNetworkCallback(networkRequest, networkCallback); 378 } 379 } 380 381 /** 382 * Registers networkCallback to receive notifications about default network. 383 * Only callable on P and newer releases. 384 */ 385 @TargetApi(Build.VERSION_CODES.P) 386 void registerDefaultNetworkCallback(NetworkCallback networkCallback, Handler handler) { 387 mConnectivityManager.registerDefaultNetworkCallback(networkCallback, handler); 388 } 389 390 /** 391 * Unregisters networkCallback from receiving notifications. 392 * Only callable on Lollipop and newer releases. 393 */ 394 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 395 void unregisterNetworkCallback(NetworkCallback networkCallback) { 396 mConnectivityManager.unregisterNetworkCallback(networkCallback); 397 } 398 399 /** 400 * Returns the current default {@link Network}, or {@code null} if disconnected. 401 * Only callable on Lollipop and newer releases. 402 */ 403 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 404 Network getDefaultNetwork() { 405 Network defaultNetwork = null; 406 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 407 defaultNetwork = ApiHelperForM.getActiveNetwork(mConnectivityManager); 408 // getActiveNetwork() returning null cannot be trusted to indicate disconnected 409 // as it suffers from https://crbug.com/677365. 410 if (defaultNetwork != null) { 411 return defaultNetwork; 412 } 413 } 414 // Android Lollipop had no API to get the default network; only an 415 // API to return the NetworkInfo for the default network. To 416 // determine the default network one can find the network with 417 // type matching that of the default network. 418 final NetworkInfo defaultNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); 419 if (defaultNetworkInfo == null) { 420 return null; 421 } 422 final Network[] networks = getAllNetworksFiltered(this, null); 423 for (Network network : networks) { 424 final NetworkInfo networkInfo = getNetworkInfo(network); 425 if (networkInfo != null 426 && (networkInfo.getType() == defaultNetworkInfo.getType() 427 // getActiveNetworkInfo() will not return TYPE_VPN types due to 428 // https://android.googlesource.com/platform/frameworks/base/+/d6a7980d 429 // so networkInfo.getType() can't be matched against 430 // defaultNetworkInfo.getType() but networkInfo.getType() should 431 // be TYPE_VPN. In the case of a VPN, getAllNetworks() will have 432 // returned just this VPN if it applies. 433 || networkInfo.getType() == TYPE_VPN)) { 434 // There should not be multiple connected networks of the 435 // same type. At least as of Android Marshmallow this is 436 // not supported. If this becomes supported this assertion 437 // may trigger. 438 assert defaultNetwork == null; 439 defaultNetwork = network; 440 } 441 } 442 return defaultNetwork; 443 } 444 } 445 446 /** Queries the WifiManager for SSID of the current Wifi connection. */ 447 static class WifiManagerDelegate { 448 private final Context mContext; 449 // Lock all members below. 450 private final Object mLock = new Object(); 451 // Has mHasWifiPermission been calculated. 452 @GuardedBy("mLock") 453 private boolean mHasWifiPermissionComputed; 454 // Only valid when mHasWifiPermissionComputed is set. 455 @GuardedBy("mLock") 456 private boolean mHasWifiPermission; 457 // Only valid when mHasWifiPermission is set. 458 @GuardedBy("mLock") 459 private WifiManager mWifiManager; 460 461 WifiManagerDelegate(Context context) { 462 // Getting SSID requires more permissions in later Android releases. 463 assert Build.VERSION.SDK_INT < Build.VERSION_CODES.M; 464 mContext = context; 465 } 466 467 // For testing. 468 WifiManagerDelegate() { 469 // All the methods below should be overridden. 470 mContext = null; 471 } 472 473 // Lazily determine if app has ACCESS_WIFI_STATE permission. 474 @GuardedBy("mLock") 475 @SuppressLint("WifiManagerPotentialLeak") 476 private boolean hasPermissionLocked() { 477 if (mHasWifiPermissionComputed) { 478 return mHasWifiPermission; 479 } 480 mHasWifiPermission = mContext.getPackageManager().checkPermission( 481 permission.ACCESS_WIFI_STATE, mContext.getPackageName()) 482 == PackageManager.PERMISSION_GRANTED; 483 // TODO(crbug.com/635567): Fix lint properly. 484 mWifiManager = mHasWifiPermission 485 ? (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE) 486 : null; 487 mHasWifiPermissionComputed = true; 488 return mHasWifiPermission; 489 } 490 491 String getWifiSsid() { 492 // Synchronized because this method can be called on multiple threads (e.g. mLooper 493 // from a private caller, and another thread calling a public API like 494 // getCurrentNetworkState) and is otherwise racy. 495 synchronized (mLock) { 496 // If app has permission it's faster to query WifiManager directly. 497 if (hasPermissionLocked()) { 498 WifiInfo wifiInfo = getWifiInfoLocked(); 499 if (wifiInfo != null) { 500 return wifiInfo.getSSID(); 501 } 502 return ""; 503 } 504 } 505 return AndroidNetworkLibrary.getWifiSSID(); 506 } 507 508 // Fetches WifiInfo and records UMA for NullPointerExceptions. 509 @GuardedBy("mLock") 510 private WifiInfo getWifiInfoLocked() { 511 try { 512 return mWifiManager.getConnectionInfo(); 513 } catch (NullPointerException firstException) { 514 // Rarely this unexpectedly throws. Retry or just return {@code null} if it fails. 515 try { 516 return mWifiManager.getConnectionInfo(); 517 } catch (NullPointerException secondException) { 518 return null; 519 } 520 } 521 } 522 } 523 524 // NetworkCallback used for listening for changes to the default network. 525 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 526 private class DefaultNetworkCallback extends NetworkCallback { 527 // If registered, notify connectionTypeChanged() to look for changes. 528 @Override 529 public void onAvailable(Network network) { 530 if (mRegistered) { 531 connectionTypeChanged(); 532 } 533 } 534 535 @Override 536 public void onLost(final Network network) { 537 onAvailable(null); 538 } 539 540 // LinkProperties changes include enabling/disabling DNS-over-TLS. 541 @Override 542 public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { 543 onAvailable(null); 544 } 545 } 546 547 // This class gets called back by ConnectivityManager whenever networks come 548 // and go. It gets called back on a special handler thread 549 // ConnectivityManager creates for making the callbacks. The callbacks in 550 // turn post to mLooper where mObserver lives. 551 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 552 private class MyNetworkCallback extends NetworkCallback { 553 // If non-null, this indicates a VPN is in place for the current user, and no other 554 // networks are accessible. 555 private Network mVpnInPlace; 556 557 // Initialize mVpnInPlace. 558 void initializeVpnInPlace() { 559 final Network[] networks = getAllNetworksFiltered(mConnectivityManagerDelegate, null); 560 mVpnInPlace = null; 561 // If the filtered list of networks contains just a VPN, then that VPN is in place. 562 if (networks.length == 1) { 563 final NetworkCapabilities capabilities = 564 mConnectivityManagerDelegate.getNetworkCapabilities(networks[0]); 565 if (capabilities != null && capabilities.hasTransport(TRANSPORT_VPN)) { 566 mVpnInPlace = networks[0]; 567 } 568 } 569 } 570 571 /** 572 * Should changes to network {@code network} be ignored due to a VPN being in place 573 * and blocking direct access to {@code network}? 574 * @param network Network to possibly consider ignoring changes to. 575 */ 576 private boolean ignoreNetworkDueToVpn(Network network) { 577 return mVpnInPlace != null && !mVpnInPlace.equals(network); 578 } 579 580 /** 581 * Should changes to connected network {@code network} be ignored? 582 * @param network Network to possibly consider ignoring changes to. 583 * @param capabilities {@code NetworkCapabilities} for {@code network} if known, otherwise 584 * {@code null}. 585 * @return {@code true} when either: {@code network} is an inaccessible VPN, or has already 586 * disconnected. 587 */ 588 private boolean ignoreConnectedInaccessibleVpn( 589 Network network, NetworkCapabilities capabilities) { 590 // Fetch capabilities if not provided. 591 if (capabilities == null) { 592 capabilities = mConnectivityManagerDelegate.getNetworkCapabilities(network); 593 } 594 // Ignore inaccessible VPNs as they don't apply to Chrome. 595 return capabilities == null 596 || capabilities.hasTransport(TRANSPORT_VPN) 597 && !mConnectivityManagerDelegate.vpnAccessible(network); 598 } 599 600 /** 601 * Should changes to connected network {@code network} be ignored? 602 * @param network Network to possible consider ignoring changes to. 603 * @param capabilities {@code NetworkCapabilities} for {@code network} if known, otherwise 604 * {@code null}. 605 */ 606 private boolean ignoreConnectedNetwork(Network network, NetworkCapabilities capabilities) { 607 return ignoreNetworkDueToVpn(network) 608 || ignoreConnectedInaccessibleVpn(network, capabilities); 609 } 610 611 @Override 612 public void onAvailable(Network network) { 613 final NetworkCapabilities capabilities = 614 mConnectivityManagerDelegate.getNetworkCapabilities(network); 615 if (ignoreConnectedNetwork(network, capabilities)) { 616 return; 617 } 618 final boolean makeVpnDefault = capabilities.hasTransport(TRANSPORT_VPN) && 619 // Only make the VPN the default if it isn't already. 620 (mVpnInPlace == null || !network.equals(mVpnInPlace)); 621 if (makeVpnDefault) { 622 mVpnInPlace = network; 623 } 624 final long netId = networkToNetId(network); 625 @ConnectionType 626 final int connectionType = mConnectivityManagerDelegate.getConnectionType(network); 627 runOnThread(new Runnable() { 628 @Override 629 public void run() { 630 mObserver.onNetworkConnect(netId, connectionType); 631 if (makeVpnDefault) { 632 // Make VPN the default network. 633 mObserver.onConnectionTypeChanged(connectionType); 634 // Purge all other networks as they're inaccessible to Chrome now. 635 mObserver.purgeActiveNetworkList(new long[] {netId}); 636 } 637 } 638 }); 639 } 640 641 @Override 642 public void onCapabilitiesChanged( 643 Network network, NetworkCapabilities networkCapabilities) { 644 if (ignoreConnectedNetwork(network, networkCapabilities)) { 645 return; 646 } 647 // A capabilities change may indicate the ConnectionType has changed, 648 // so forward the new ConnectionType along to observer. 649 final long netId = networkToNetId(network); 650 final int connectionType = mConnectivityManagerDelegate.getConnectionType(network); 651 runOnThread(new Runnable() { 652 @Override 653 public void run() { 654 mObserver.onNetworkConnect(netId, connectionType); 655 } 656 }); 657 } 658 659 @Override 660 public void onLosing(Network network, int maxMsToLive) { 661 if (ignoreConnectedNetwork(network, null)) { 662 return; 663 } 664 final long netId = networkToNetId(network); 665 runOnThread(new Runnable() { 666 @Override 667 public void run() { 668 mObserver.onNetworkSoonToDisconnect(netId); 669 } 670 }); 671 } 672 673 @Override 674 public void onLost(final Network network) { 675 if (ignoreNetworkDueToVpn(network)) { 676 return; 677 } 678 runOnThread(new Runnable() { 679 @Override 680 public void run() { 681 mObserver.onNetworkDisconnect(networkToNetId(network)); 682 } 683 }); 684 // If the VPN is going away, inform observer that other networks that were previously 685 // hidden by ignoreNetworkDueToVpn() are now available for use, now that this user's 686 // traffic is not forced into the VPN. 687 if (mVpnInPlace != null) { 688 assert network.equals(mVpnInPlace); 689 mVpnInPlace = null; 690 for (Network newNetwork : 691 getAllNetworksFiltered(mConnectivityManagerDelegate, network)) { 692 onAvailable(newNetwork); 693 } 694 @ConnectionType 695 final int newConnectionType = getCurrentNetworkState().getConnectionType(); 696 runOnThread(new Runnable() { 697 @Override 698 public void run() { 699 mObserver.onConnectionTypeChanged(newConnectionType); 700 } 701 }); 702 } 703 } 704 } 705 706 /** 707 * Abstract class for providing a policy regarding when the NetworkChangeNotifier 708 * should listen for network changes. 709 */ 710 public abstract static class RegistrationPolicy { 711 private NetworkChangeNotifierAutoDetect mNotifier; 712 713 /** 714 * Start listening for network changes. 715 */ 716 protected final void register() { 717 assert mNotifier != null; 718 mNotifier.register(); 719 } 720 721 /** 722 * Stop listening for network changes. 723 */ 724 protected final void unregister() { 725 assert mNotifier != null; 726 mNotifier.unregister(); 727 } 728 729 /** 730 * Initializes the policy with the notifier, overriding subclasses should always 731 * call this method. 732 */ 733 protected void init(NetworkChangeNotifierAutoDetect notifier) { 734 mNotifier = notifier; 735 } 736 737 protected abstract void destroy(); 738 } 739 740 private static final String TAG = NetworkChangeNotifierAutoDetect.class.getSimpleName(); 741 private static final int UNKNOWN_LINK_SPEED = -1; 742 743 // {@link Looper} for the thread this object lives on. 744 private final Looper mLooper; 745 // Used to post to the thread this object lives on. 746 private final Handler mHandler; 747 // {@link IntentFilter} for incoming global broadcast {@link Intent}s this object listens for. 748 private final NetworkConnectivityIntentFilter mIntentFilter; 749 // Notifications are sent to this {@link Observer}. 750 private final Observer mObserver; 751 private final RegistrationPolicy mRegistrationPolicy; 752 // Starting with Android Pie, used to detect changes in default network. 753 private DefaultNetworkCallback mDefaultNetworkCallback; 754 755 // mConnectivityManagerDelegates and mWifiManagerDelegate are only non-final for testing. 756 private ConnectivityManagerDelegate mConnectivityManagerDelegate; 757 private WifiManagerDelegate mWifiManagerDelegate; 758 // mNetworkCallback and mNetworkRequest are only non-null in Android L and above. 759 // mNetworkCallback will be null if ConnectivityManager.registerNetworkCallback() ever fails. 760 private MyNetworkCallback mNetworkCallback; 761 private NetworkRequest mNetworkRequest; 762 private boolean mRegistered; 763 private NetworkState mNetworkState; 764 // When a BroadcastReceiver is registered for a sticky broadcast that has been sent out at 765 // least once, onReceive() will immediately be called. mIgnoreNextBroadcast is set to true 766 // when this class is registered in such a circumstance, and indicates that the next 767 // invokation of onReceive() can be ignored as the state hasn't actually changed. Immediately 768 // prior to mIgnoreNextBroadcast being set, all internal state is updated to the current device 769 // state so were this initial onReceive() call not ignored, no signals would be passed to 770 // observers anyhow as the state hasn't changed. This is simply an optimization to avoid 771 // useless work. 772 private boolean mIgnoreNextBroadcast; 773 // mSignal is set to false when it's not worth calculating if signals to Observers should 774 // be sent out because this class is being constructed and the internal state has just 775 // been updated to the current device state, so no signals are necessary. This is simply an 776 // optimization to avoid useless work. 777 private boolean mShouldSignalObserver; 778 // Indicates if ConnectivityManager.registerNetworkRequest() ever failed. When true, no 779 // network-specific callbacks (e.g. Observer.onNetwork*() ) will be issued. 780 private boolean mRegisterNetworkCallbackFailed; 781 782 /** 783 * Observer interface by which observer is notified of network changes. 784 */ 785 public static interface Observer { 786 /** 787 * Called when default network changes. 788 */ 789 public void onConnectionTypeChanged(@ConnectionType int newConnectionType); 790 /** 791 * Called when connection subtype of default network changes. 792 */ 793 public void onConnectionSubtypeChanged(int newConnectionSubtype); 794 /** 795 * Called when device connects to network with NetID netId. For 796 * example device associates with a WiFi access point. 797 * connectionType is the type of the network; a member of 798 * ConnectionType. Only called on Android L and above. 799 */ 800 public void onNetworkConnect(long netId, int connectionType); 801 /** 802 * Called when device determines the connection to the network with 803 * NetID netId is no longer preferred, for example when a device 804 * transitions from cellular to WiFi it might deem the cellular 805 * connection no longer preferred. The device will disconnect from 806 * the network in 30s allowing network communications on that network 807 * to wrap up. Only called on Android L and above. 808 */ 809 public void onNetworkSoonToDisconnect(long netId); 810 /** 811 * Called when device disconnects from network with NetID netId. 812 * Only called on Android L and above. 813 */ 814 public void onNetworkDisconnect(long netId); 815 /** 816 * Called to cause a purge of cached lists of active networks, of any 817 * networks not in the accompanying list of active networks. This is 818 * issued if a period elapsed where disconnected notifications may have 819 * been missed, and acts to keep cached lists of active networks 820 * accurate. Only called on Android L and above. 821 */ 822 public void purgeActiveNetworkList(long[] activeNetIds); 823 } 824 825 /** 826 * Constructs a NetworkChangeNotifierAutoDetect. Lives on calling thread, receives broadcast 827 * notifications on the UI thread and forwards the notifications to be processed on the calling 828 * thread. 829 * @param policy The RegistrationPolicy which determines when this class should watch 830 * for network changes (e.g. see (@link RegistrationPolicyAlwaysRegister} and 831 * {@link RegistrationPolicyApplicationStatus}). 832 */ 833 @TargetApi(Build.VERSION_CODES.LOLLIPOP) NetworkChangeNotifierAutoDetect(Observer observer, RegistrationPolicy policy)834 public NetworkChangeNotifierAutoDetect(Observer observer, RegistrationPolicy policy) { 835 mLooper = Looper.myLooper(); 836 mHandler = new Handler(mLooper); 837 mObserver = observer; 838 mConnectivityManagerDelegate = 839 new ConnectivityManagerDelegate(ContextUtils.getApplicationContext()); 840 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 841 mWifiManagerDelegate = new WifiManagerDelegate(ContextUtils.getApplicationContext()); 842 } 843 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 844 mNetworkCallback = new MyNetworkCallback(); 845 mNetworkRequest = new NetworkRequest.Builder() 846 .addCapability(NET_CAPABILITY_INTERNET) 847 // Need to hear about VPNs too. 848 .removeCapability(NET_CAPABILITY_NOT_VPN) 849 .build(); 850 } else { 851 mNetworkCallback = null; 852 mNetworkRequest = null; 853 } 854 mDefaultNetworkCallback = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 855 ? new DefaultNetworkCallback() 856 : null; 857 mNetworkState = getCurrentNetworkState(); 858 mIntentFilter = new NetworkConnectivityIntentFilter(); 859 mIgnoreNextBroadcast = false; 860 mShouldSignalObserver = false; 861 mRegistrationPolicy = policy; 862 mRegistrationPolicy.init(this); 863 mShouldSignalObserver = true; 864 } 865 onThread()866 private boolean onThread() { 867 return mLooper == Looper.myLooper(); 868 } 869 assertOnThread()870 private void assertOnThread() { 871 if (BuildConfig.DCHECK_IS_ON && !onThread()) { 872 throw new IllegalStateException( 873 "Must be called on NetworkChangeNotifierAutoDetect thread."); 874 } 875 } 876 runOnThread(Runnable r)877 private void runOnThread(Runnable r) { 878 if (onThread()) { 879 r.run(); 880 } else { 881 mHandler.post(r); 882 } 883 } 884 885 /** 886 * Allows overriding the ConnectivityManagerDelegate for tests. 887 */ setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate)888 void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) { 889 mConnectivityManagerDelegate = delegate; 890 } 891 892 /** 893 * Allows overriding the WifiManagerDelegate for tests. 894 */ setWifiManagerDelegateForTests(WifiManagerDelegate delegate)895 void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) { 896 mWifiManagerDelegate = delegate; 897 } 898 899 @VisibleForTesting getRegistrationPolicy()900 RegistrationPolicy getRegistrationPolicy() { 901 return mRegistrationPolicy; 902 } 903 904 /** 905 * Returns whether the object has registered to receive network connectivity intents. 906 */ 907 @VisibleForTesting isReceiverRegisteredForTesting()908 boolean isReceiverRegisteredForTesting() { 909 return mRegistered; 910 } 911 destroy()912 public void destroy() { 913 assertOnThread(); 914 mRegistrationPolicy.destroy(); 915 unregister(); 916 } 917 918 /** 919 * Registers a BroadcastReceiver in the given context. 920 */ register()921 public void register() { 922 assertOnThread(); 923 if (mRegistered) { 924 // Even when registered previously, Android may not send callbacks about change of 925 // network state when the device screen is turned on from off. Get the most up-to-date 926 // network state. See https://crbug.com/1007998 for more details. 927 connectionTypeChanged(); 928 return; 929 } 930 931 if (mShouldSignalObserver) { 932 connectionTypeChanged(); 933 } 934 if (mDefaultNetworkCallback != null) { 935 try { 936 mConnectivityManagerDelegate.registerDefaultNetworkCallback( 937 mDefaultNetworkCallback, mHandler); 938 } catch (RuntimeException e) { 939 // If registering a default network callback failed, fallback to 940 // listening for CONNECTIVITY_ACTION broadcast. 941 mDefaultNetworkCallback = null; 942 } 943 } 944 if (mDefaultNetworkCallback == null) { 945 // When registering for a sticky broadcast, like CONNECTIVITY_ACTION, if 946 // registerReceiver returns non-null, it means the broadcast was previously issued and 947 // onReceive() will be immediately called with this previous Intent. Since this initial 948 // callback doesn't actually indicate a network change, we can ignore it by setting 949 // mIgnoreNextBroadcast. 950 mIgnoreNextBroadcast = 951 ContextUtils.getApplicationContext().registerReceiver(this, mIntentFilter) 952 != null; 953 } 954 mRegistered = true; 955 956 if (mNetworkCallback != null) { 957 mNetworkCallback.initializeVpnInPlace(); 958 try { 959 mConnectivityManagerDelegate.registerNetworkCallback( 960 mNetworkRequest, mNetworkCallback, mHandler); 961 } catch (RuntimeException e) { 962 mRegisterNetworkCallbackFailed = true; 963 // If Android thinks this app has used up all available NetworkRequests, don't 964 // bother trying to register any more callbacks as Android will still think 965 // all available NetworkRequests are used up and fail again needlessly. 966 // Also don't bother unregistering as this call didn't actually register. 967 // See crbug.com/791025 for more info. 968 mNetworkCallback = null; 969 } 970 if (!mRegisterNetworkCallbackFailed && mShouldSignalObserver) { 971 // registerNetworkCallback() will rematch the NetworkRequest 972 // against active networks, so a cached list of active networks 973 // will be repopulated immediatly after this. However we need to 974 // purge any cached networks as they may have been disconnected 975 // while mNetworkCallback was unregistered. 976 final Network[] networks = 977 getAllNetworksFiltered(mConnectivityManagerDelegate, null); 978 // Convert Networks to NetIDs. 979 final long[] netIds = new long[networks.length]; 980 for (int i = 0; i < networks.length; i++) { 981 netIds[i] = networkToNetId(networks[i]); 982 } 983 mObserver.purgeActiveNetworkList(netIds); 984 } 985 } 986 } 987 988 /** 989 * Unregisters a BroadcastReceiver in the given context. 990 */ unregister()991 public void unregister() { 992 assertOnThread(); 993 if (!mRegistered) return; 994 mRegistered = false; 995 if (mNetworkCallback != null) { 996 mConnectivityManagerDelegate.unregisterNetworkCallback(mNetworkCallback); 997 } 998 if (mDefaultNetworkCallback != null) { 999 mConnectivityManagerDelegate.unregisterNetworkCallback(mDefaultNetworkCallback); 1000 } else { 1001 ContextUtils.getApplicationContext().unregisterReceiver(this); 1002 } 1003 } 1004 getCurrentNetworkState()1005 public NetworkState getCurrentNetworkState() { 1006 return mConnectivityManagerDelegate.getNetworkState(mWifiManagerDelegate); 1007 } 1008 1009 /** 1010 * Returns all connected networks that are useful and accessible to Chrome. 1011 * Only callable on Lollipop and newer releases. 1012 * @param ignoreNetwork ignore this network as if it is not connected. 1013 */ 1014 @TargetApi(Build.VERSION_CODES.LOLLIPOP) getAllNetworksFiltered( ConnectivityManagerDelegate connectivityManagerDelegate, Network ignoreNetwork)1015 private static Network[] getAllNetworksFiltered( 1016 ConnectivityManagerDelegate connectivityManagerDelegate, Network ignoreNetwork) { 1017 Network[] networks = connectivityManagerDelegate.getAllNetworksUnfiltered(); 1018 // Whittle down |networks| into just the list of networks useful to us. 1019 int filteredIndex = 0; 1020 for (Network network : networks) { 1021 if (network.equals(ignoreNetwork)) { 1022 continue; 1023 } 1024 final NetworkCapabilities capabilities = 1025 connectivityManagerDelegate.getNetworkCapabilities(network); 1026 if (capabilities == null || !capabilities.hasCapability(NET_CAPABILITY_INTERNET)) { 1027 continue; 1028 } 1029 if (capabilities.hasTransport(TRANSPORT_VPN)) { 1030 // If we can access the VPN then... 1031 if (connectivityManagerDelegate.vpnAccessible(network)) { 1032 // ...we cannot access any other network, so return just the VPN. 1033 return new Network[] {network}; 1034 } else { 1035 // ...otherwise ignore it as we cannot use it. 1036 continue; 1037 } 1038 } 1039 networks[filteredIndex++] = network; 1040 } 1041 return Arrays.copyOf(networks, filteredIndex); 1042 } 1043 1044 /** 1045 * Returns an array of all of the device's currently connected 1046 * networks and ConnectionTypes, including only those that are useful and accessible to Chrome. 1047 * Array elements are a repeated sequence of: 1048 * NetID of network 1049 * ConnectionType of network 1050 * Only available on Lollipop and newer releases and when auto-detection has 1051 * been enabled. 1052 */ getNetworksAndTypes()1053 public long[] getNetworksAndTypes() { 1054 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 1055 return new long[0]; 1056 } 1057 final Network networks[] = getAllNetworksFiltered(mConnectivityManagerDelegate, null); 1058 final long networksAndTypes[] = new long[networks.length * 2]; 1059 int index = 0; 1060 for (Network network : networks) { 1061 networksAndTypes[index++] = networkToNetId(network); 1062 networksAndTypes[index++] = mConnectivityManagerDelegate.getConnectionType(network); 1063 } 1064 return networksAndTypes; 1065 } 1066 1067 /** 1068 * Returns NetID of device's current default connected network used for 1069 * communication. 1070 * Only implemented on Lollipop and newer releases, returns NetId.INVALID 1071 * when not implemented. 1072 */ getDefaultNetId()1073 public long getDefaultNetId() { 1074 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 1075 return NetId.INVALID; 1076 } 1077 Network network = mConnectivityManagerDelegate.getDefaultNetwork(); 1078 return network == null ? NetId.INVALID : networkToNetId(network); 1079 } 1080 1081 /** 1082 * Returns {@code true} if NetworkCallback failed to register, indicating that network-specific 1083 * callbacks will not be issued. 1084 */ registerNetworkCallbackFailed()1085 public boolean registerNetworkCallbackFailed() { 1086 return mRegisterNetworkCallbackFailed; 1087 } 1088 1089 /** 1090 * Returns the connection type for the given ConnectivityManager type and subtype. 1091 */ 1092 @ConnectionType convertToConnectionType(int type, int subtype)1093 private static int convertToConnectionType(int type, int subtype) { 1094 switch (type) { 1095 case ConnectivityManager.TYPE_ETHERNET: 1096 return ConnectionType.CONNECTION_ETHERNET; 1097 case ConnectivityManager.TYPE_WIFI: 1098 return ConnectionType.CONNECTION_WIFI; 1099 case ConnectivityManager.TYPE_WIMAX: 1100 return ConnectionType.CONNECTION_4G; 1101 case ConnectivityManager.TYPE_BLUETOOTH: 1102 return ConnectionType.CONNECTION_BLUETOOTH; 1103 case ConnectivityManager.TYPE_MOBILE: 1104 case ConnectivityManager.TYPE_MOBILE_DUN: 1105 case ConnectivityManager.TYPE_MOBILE_HIPRI: 1106 // Use information from TelephonyManager to classify the connection. 1107 switch (subtype) { 1108 case TelephonyManager.NETWORK_TYPE_GPRS: 1109 case TelephonyManager.NETWORK_TYPE_EDGE: 1110 case TelephonyManager.NETWORK_TYPE_CDMA: 1111 case TelephonyManager.NETWORK_TYPE_1xRTT: 1112 case TelephonyManager.NETWORK_TYPE_IDEN: 1113 return ConnectionType.CONNECTION_2G; 1114 case TelephonyManager.NETWORK_TYPE_UMTS: 1115 case TelephonyManager.NETWORK_TYPE_EVDO_0: 1116 case TelephonyManager.NETWORK_TYPE_EVDO_A: 1117 case TelephonyManager.NETWORK_TYPE_HSDPA: 1118 case TelephonyManager.NETWORK_TYPE_HSUPA: 1119 case TelephonyManager.NETWORK_TYPE_HSPA: 1120 case TelephonyManager.NETWORK_TYPE_EVDO_B: 1121 case TelephonyManager.NETWORK_TYPE_EHRPD: 1122 case TelephonyManager.NETWORK_TYPE_HSPAP: 1123 return ConnectionType.CONNECTION_3G; 1124 case TelephonyManager.NETWORK_TYPE_LTE: 1125 return ConnectionType.CONNECTION_4G; 1126 case TelephonyManager.NETWORK_TYPE_NR: 1127 return ConnectionType.CONNECTION_5G; 1128 default: 1129 return ConnectionType.CONNECTION_UNKNOWN; 1130 } 1131 default: 1132 return ConnectionType.CONNECTION_UNKNOWN; 1133 } 1134 } 1135 1136 // BroadcastReceiver 1137 @Override onReceive(Context context, Intent intent)1138 public void onReceive(Context context, Intent intent) { 1139 runOnThread(new Runnable() { 1140 @Override 1141 public void run() { 1142 // Once execution begins on the correct thread, make sure unregister() hasn't 1143 // been called in the mean time. Ignore the broadcast if unregister() was called. 1144 if (!mRegistered) { 1145 return; 1146 } 1147 if (mIgnoreNextBroadcast) { 1148 mIgnoreNextBroadcast = false; 1149 return; 1150 } 1151 connectionTypeChanged(); 1152 } 1153 }); 1154 } 1155 connectionTypeChanged()1156 private void connectionTypeChanged() { 1157 NetworkState networkState = getCurrentNetworkState(); 1158 if (networkState.getConnectionType() != mNetworkState.getConnectionType() 1159 || !networkState.getNetworkIdentifier().equals(mNetworkState.getNetworkIdentifier()) 1160 || networkState.isPrivateDnsActive() != mNetworkState.isPrivateDnsActive() 1161 || !networkState.getPrivateDnsServerName().equals( 1162 mNetworkState.getPrivateDnsServerName())) { 1163 mObserver.onConnectionTypeChanged(networkState.getConnectionType()); 1164 } 1165 if (networkState.getConnectionType() != mNetworkState.getConnectionType() 1166 || networkState.getConnectionSubtype() != mNetworkState.getConnectionSubtype()) { 1167 mObserver.onConnectionSubtypeChanged(networkState.getConnectionSubtype()); 1168 } 1169 mNetworkState = networkState; 1170 } 1171 1172 private static class NetworkConnectivityIntentFilter extends IntentFilter { NetworkConnectivityIntentFilter()1173 NetworkConnectivityIntentFilter() { 1174 addAction(ConnectivityManager.CONNECTIVITY_ACTION); 1175 } 1176 } 1177 1178 /** 1179 * Extracts NetID of Network on Lollipop and NetworkHandle (which is munged NetID) on 1180 * Marshmallow and newer releases. Only available on Lollipop and newer releases. 1181 */ 1182 @TargetApi(Build.VERSION_CODES.LOLLIPOP) networkToNetId(Network network)1183 public static long networkToNetId(Network network) { 1184 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 1185 return ApiHelperForM.getNetworkHandle(network); 1186 } else { 1187 // NOTE(pauljensen): This depends on Android framework implementation details. These 1188 // details cannot change because Lollipop is long since released. 1189 // NetIDs are only 16-bit so use parseInt. This function returns a long because 1190 // getNetworkHandle() returns a long. 1191 return Integer.parseInt(network.toString()); 1192 } 1193 } 1194 } 1195