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