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