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