1 /* 2 * libjingle 3 * Copyright 2015 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 package org.webrtc; 29 30 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 31 32 import android.Manifest.permission; 33 import android.annotation.SuppressLint; 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.content.pm.PackageManager; 39 import android.net.ConnectivityManager; 40 import android.net.Network; 41 import android.net.NetworkCapabilities; 42 import android.net.NetworkInfo; 43 import android.net.wifi.WifiInfo; 44 import android.net.wifi.WifiManager; 45 import android.os.Build; 46 import android.telephony.TelephonyManager; 47 import android.util.Log; 48 49 /** 50 * Borrowed from Chromium's 51 * src/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java 52 * 53 * Used by the NetworkMonitor to listen to platform changes in connectivity. 54 * Note that use of this class requires that the app have the platform 55 * ACCESS_NETWORK_STATE permission. 56 */ 57 public class NetworkMonitorAutoDetect extends BroadcastReceiver { 58 public static enum ConnectionType { 59 CONNECTION_UNKNOWN, 60 CONNECTION_ETHERNET, 61 CONNECTION_WIFI, 62 CONNECTION_4G, 63 CONNECTION_3G, 64 CONNECTION_2G, 65 CONNECTION_BLUETOOTH, 66 CONNECTION_NONE 67 } 68 69 static class NetworkState { 70 private final boolean connected; 71 // Defined from ConnectivityManager.TYPE_XXX for non-mobile; for mobile, it is 72 // further divided into 2G, 3G, or 4G from the subtype. 73 private final int type; 74 // Defined from NetworkInfo.subtype, which is one of the TelephonyManager.NETWORK_TYPE_XXXs. 75 // Will be useful to find the maximum bandwidth. 76 private final int subtype; 77 NetworkState(boolean connected, int type, int subtype)78 public NetworkState(boolean connected, int type, int subtype) { 79 this.connected = connected; 80 this.type = type; 81 this.subtype = subtype; 82 } 83 isConnected()84 public boolean isConnected() { 85 return connected; 86 } 87 getNetworkType()88 public int getNetworkType() { 89 return type; 90 } 91 getNetworkSubType()92 public int getNetworkSubType() { 93 return subtype; 94 } 95 } 96 97 /** Queries the ConnectivityManager for information about the current connection. */ 98 static class ConnectivityManagerDelegate { 99 /** 100 * Note: In some rare Android systems connectivityManager is null. We handle that 101 * gracefully below. 102 */ 103 private final ConnectivityManager connectivityManager; 104 ConnectivityManagerDelegate(Context context)105 ConnectivityManagerDelegate(Context context) { 106 connectivityManager = 107 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 108 } 109 110 // For testing. ConnectivityManagerDelegate()111 ConnectivityManagerDelegate() { 112 // All the methods below should be overridden. 113 connectivityManager = null; 114 } 115 116 /** 117 * Returns connection type and status information about the current 118 * default network. 119 */ getNetworkState()120 NetworkState getNetworkState() { 121 if (connectivityManager == null) { 122 return new NetworkState(false, -1, -1); 123 } 124 return getNetworkState(connectivityManager.getActiveNetworkInfo()); 125 } 126 127 /** 128 * Returns connection type and status information about |network|. 129 * Only callable on Lollipop and newer releases. 130 */ 131 @SuppressLint("NewApi") getNetworkState(Network network)132 NetworkState getNetworkState(Network network) { 133 if (connectivityManager == null) { 134 return new NetworkState(false, -1, -1); 135 } 136 return getNetworkState(connectivityManager.getNetworkInfo(network)); 137 } 138 139 /** 140 * Returns connection type and status information gleaned from networkInfo. 141 */ getNetworkState(NetworkInfo networkInfo)142 NetworkState getNetworkState(NetworkInfo networkInfo) { 143 if (networkInfo == null || !networkInfo.isConnected()) { 144 return new NetworkState(false, -1, -1); 145 } 146 return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype()); 147 } 148 149 /** 150 * Returns all connected networks. 151 * Only callable on Lollipop and newer releases. 152 */ 153 @SuppressLint("NewApi") getAllNetworks()154 Network[] getAllNetworks() { 155 if (connectivityManager == null) { 156 return new Network[0]; 157 } 158 return connectivityManager.getAllNetworks(); 159 } 160 161 /** 162 * Returns the NetID of the current default network. Returns 163 * INVALID_NET_ID if no current default network connected. 164 * Only callable on Lollipop and newer releases. 165 */ 166 @SuppressLint("NewApi") getDefaultNetId()167 int getDefaultNetId() { 168 if (connectivityManager == null) { 169 return INVALID_NET_ID; 170 } 171 // Android Lollipop had no API to get the default network; only an 172 // API to return the NetworkInfo for the default network. To 173 // determine the default network one can find the network with 174 // type matching that of the default network. 175 final NetworkInfo defaultNetworkInfo = connectivityManager.getActiveNetworkInfo(); 176 if (defaultNetworkInfo == null) { 177 return INVALID_NET_ID; 178 } 179 final Network[] networks = getAllNetworks(); 180 int defaultNetId = INVALID_NET_ID; 181 for (Network network : networks) { 182 if (!hasInternetCapability(network)) { 183 continue; 184 } 185 final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); 186 if (networkInfo != null && networkInfo.getType() == defaultNetworkInfo.getType()) { 187 // There should not be multiple connected networks of the 188 // same type. At least as of Android Marshmallow this is 189 // not supported. If this becomes supported this assertion 190 // may trigger. At that point we could consider using 191 // ConnectivityManager.getDefaultNetwork() though this 192 // may give confusing results with VPNs and is only 193 // available with Android Marshmallow. 194 assert defaultNetId == INVALID_NET_ID; 195 defaultNetId = networkToNetId(network); 196 } 197 } 198 return defaultNetId; 199 } 200 201 /** 202 * Returns true if {@code network} can provide Internet access. Can be used to 203 * ignore specialized networks (e.g. IMS, FOTA). 204 */ 205 @SuppressLint("NewApi") hasInternetCapability(Network network)206 boolean hasInternetCapability(Network network) { 207 if (connectivityManager == null) { 208 return false; 209 } 210 final NetworkCapabilities capabilities = 211 connectivityManager.getNetworkCapabilities(network); 212 return capabilities != null && capabilities.hasCapability(NET_CAPABILITY_INTERNET); 213 } 214 } 215 216 /** Queries the WifiManager for SSID of the current Wifi connection. */ 217 static class WifiManagerDelegate { 218 private final Context context; 219 private final WifiManager wifiManager; 220 private final boolean hasWifiPermission; 221 WifiManagerDelegate(Context context)222 WifiManagerDelegate(Context context) { 223 this.context = context; 224 225 hasWifiPermission = context.getPackageManager().checkPermission( 226 permission.ACCESS_WIFI_STATE, context.getPackageName()) 227 == PackageManager.PERMISSION_GRANTED; 228 wifiManager = hasWifiPermission 229 ? (WifiManager) context.getSystemService(Context.WIFI_SERVICE) : null; 230 } 231 232 // For testing. WifiManagerDelegate()233 WifiManagerDelegate() { 234 // All the methods below should be overridden. 235 context = null; 236 wifiManager = null; 237 hasWifiPermission = false; 238 } 239 getWifiSSID()240 String getWifiSSID() { 241 final Intent intent = context.registerReceiver(null, 242 new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); 243 if (intent != null) { 244 final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); 245 if (wifiInfo != null) { 246 final String ssid = wifiInfo.getSSID(); 247 if (ssid != null) { 248 return ssid; 249 } 250 } 251 } 252 return ""; 253 } 254 getHasWifiPermission()255 boolean getHasWifiPermission() { 256 return hasWifiPermission; 257 } 258 } 259 260 static final int INVALID_NET_ID = -1; 261 private static final String TAG = "NetworkMonitorAutoDetect"; 262 private final IntentFilter intentFilter; 263 264 // Observer for the connection type change. 265 private final Observer observer; 266 267 private final Context context; 268 // connectivityManagerDelegates and wifiManagerDelegate are only non-final for testing. 269 private ConnectivityManagerDelegate connectivityManagerDelegate; 270 private WifiManagerDelegate wifiManagerDelegate; 271 private boolean isRegistered; 272 private ConnectionType connectionType; 273 private String wifiSSID; 274 275 /** 276 * Observer interface by which observer is notified of network changes. 277 */ 278 public static interface Observer { 279 /** 280 * Called when default network changes. 281 */ onConnectionTypeChanged(ConnectionType newConnectionType)282 public void onConnectionTypeChanged(ConnectionType newConnectionType); 283 } 284 285 /** 286 * Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread. 287 */ NetworkMonitorAutoDetect(Observer observer, Context context)288 public NetworkMonitorAutoDetect(Observer observer, Context context) { 289 this.observer = observer; 290 this.context = context; 291 connectivityManagerDelegate = new ConnectivityManagerDelegate(context); 292 wifiManagerDelegate = new WifiManagerDelegate(context); 293 294 final NetworkState networkState = connectivityManagerDelegate.getNetworkState(); 295 connectionType = getCurrentConnectionType(networkState); 296 wifiSSID = getCurrentWifiSSID(networkState); 297 intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 298 registerReceiver(); 299 } 300 301 /** 302 * Allows overriding the ConnectivityManagerDelegate for tests. 303 */ setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate)304 void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) { 305 connectivityManagerDelegate = delegate; 306 } 307 308 /** 309 * Allows overriding the WifiManagerDelegate for tests. 310 */ setWifiManagerDelegateForTests(WifiManagerDelegate delegate)311 void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) { 312 wifiManagerDelegate = delegate; 313 } 314 315 /** 316 * Returns whether the object has registered to receive network connectivity intents. 317 * Visible for testing. 318 */ isReceiverRegisteredForTesting()319 boolean isReceiverRegisteredForTesting() { 320 return isRegistered; 321 } 322 destroy()323 public void destroy() { 324 unregisterReceiver(); 325 } 326 327 /** 328 * Registers a BroadcastReceiver in the given context. 329 */ registerReceiver()330 private void registerReceiver() { 331 if (!isRegistered) { 332 isRegistered = true; 333 context.registerReceiver(this, intentFilter); 334 } 335 } 336 337 /** 338 * Unregisters the BroadcastReceiver in the given context. 339 */ unregisterReceiver()340 private void unregisterReceiver() { 341 if (isRegistered) { 342 isRegistered = false; 343 context.unregisterReceiver(this); 344 } 345 } 346 getCurrentNetworkState()347 public NetworkState getCurrentNetworkState() { 348 return connectivityManagerDelegate.getNetworkState(); 349 } 350 351 /** 352 * Returns NetID of device's current default connected network used for 353 * communication. 354 * Only implemented on Lollipop and newer releases, returns INVALID_NET_ID 355 * when not implemented. 356 */ getDefaultNetId()357 public int getDefaultNetId() { 358 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 359 return INVALID_NET_ID; 360 } 361 return connectivityManagerDelegate.getDefaultNetId(); 362 } 363 getCurrentConnectionType(NetworkState networkState)364 public ConnectionType getCurrentConnectionType(NetworkState networkState) { 365 if (!networkState.isConnected()) { 366 return ConnectionType.CONNECTION_NONE; 367 } 368 369 switch (networkState.getNetworkType()) { 370 case ConnectivityManager.TYPE_ETHERNET: 371 return ConnectionType.CONNECTION_ETHERNET; 372 case ConnectivityManager.TYPE_WIFI: 373 return ConnectionType.CONNECTION_WIFI; 374 case ConnectivityManager.TYPE_WIMAX: 375 return ConnectionType.CONNECTION_4G; 376 case ConnectivityManager.TYPE_BLUETOOTH: 377 return ConnectionType.CONNECTION_BLUETOOTH; 378 case ConnectivityManager.TYPE_MOBILE: 379 // Use information from TelephonyManager to classify the connection. 380 switch (networkState.getNetworkSubType()) { 381 case TelephonyManager.NETWORK_TYPE_GPRS: 382 case TelephonyManager.NETWORK_TYPE_EDGE: 383 case TelephonyManager.NETWORK_TYPE_CDMA: 384 case TelephonyManager.NETWORK_TYPE_1xRTT: 385 case TelephonyManager.NETWORK_TYPE_IDEN: 386 return ConnectionType.CONNECTION_2G; 387 case TelephonyManager.NETWORK_TYPE_UMTS: 388 case TelephonyManager.NETWORK_TYPE_EVDO_0: 389 case TelephonyManager.NETWORK_TYPE_EVDO_A: 390 case TelephonyManager.NETWORK_TYPE_HSDPA: 391 case TelephonyManager.NETWORK_TYPE_HSUPA: 392 case TelephonyManager.NETWORK_TYPE_HSPA: 393 case TelephonyManager.NETWORK_TYPE_EVDO_B: 394 case TelephonyManager.NETWORK_TYPE_EHRPD: 395 case TelephonyManager.NETWORK_TYPE_HSPAP: 396 return ConnectionType.CONNECTION_3G; 397 case TelephonyManager.NETWORK_TYPE_LTE: 398 return ConnectionType.CONNECTION_4G; 399 default: 400 return ConnectionType.CONNECTION_UNKNOWN; 401 } 402 default: 403 return ConnectionType.CONNECTION_UNKNOWN; 404 } 405 } 406 getCurrentWifiSSID(NetworkState networkState)407 private String getCurrentWifiSSID(NetworkState networkState) { 408 if (getCurrentConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return ""; 409 return wifiManagerDelegate.getWifiSSID(); 410 } 411 412 // BroadcastReceiver 413 @Override onReceive(Context context, Intent intent)414 public void onReceive(Context context, Intent intent) { 415 final NetworkState networkState = getCurrentNetworkState(); 416 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { 417 connectionTypeChanged(networkState); 418 } 419 } 420 connectionTypeChanged(NetworkState networkState)421 private void connectionTypeChanged(NetworkState networkState) { 422 ConnectionType newConnectionType = getCurrentConnectionType(networkState); 423 String newWifiSSID = getCurrentWifiSSID(networkState); 424 if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) return; 425 426 connectionType = newConnectionType; 427 wifiSSID = newWifiSSID; 428 Log.d(TAG, "Network connectivity changed, type is: " + connectionType); 429 observer.onConnectionTypeChanged(newConnectionType); 430 } 431 432 /** 433 * Extracts NetID of network. Only available on Lollipop and newer releases. 434 */ 435 @SuppressLint("NewApi") networkToNetId(Network network)436 private static int networkToNetId(Network network) { 437 // NOTE(pauljensen): This depends on Android framework implementation details. 438 // Fortunately this functionality is unlikely to ever change. 439 // TODO(honghaiz): When we update to Android M SDK, use Network.getNetworkHandle(). 440 return Integer.parseInt(network.toString()); 441 } 442 } 443