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