1 // Copyright 2012 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import android.annotation.SuppressLint; 8 9 import androidx.annotation.VisibleForTesting; 10 11 import org.chromium.base.ObserverList; 12 import org.chromium.base.annotations.CalledByNative; 13 import org.chromium.base.annotations.JNINamespace; 14 import org.chromium.base.annotations.NativeClassQualifiedName; 15 import org.chromium.base.annotations.NativeMethods; 16 17 import java.util.ArrayList; 18 19 /** 20 * Triggers updates to the underlying network state in Chrome. 21 * 22 * By default, connectivity is assumed and changes must be pushed from the embedder via the 23 * forceConnectivityState function. 24 * Embedders may choose to have this class auto-detect changes in network connectivity by invoking 25 * the setAutoDetectConnectivityState function. 26 * 27 * WARNING: This class is not thread-safe. 28 */ 29 @JNINamespace("net") 30 public class NetworkChangeNotifier { 31 /** 32 * Alerted when the connection type of the network changes. 33 * The alert is fired on the UI thread. 34 */ 35 public interface ConnectionTypeObserver { onConnectionTypeChanged(int connectionType)36 public void onConnectionTypeChanged(int connectionType); 37 } 38 39 private final ArrayList<Long> mNativeChangeNotifiers; 40 private final ObserverList<ConnectionTypeObserver> mConnectionTypeObservers; 41 private NetworkChangeNotifierAutoDetect mAutoDetector; 42 // Last value broadcast via ConnectionTypeChange signal. 43 private int mCurrentConnectionType = ConnectionType.CONNECTION_UNKNOWN; 44 // Last value broadcast via ConnectionCostChange signal. 45 private int mCurrentConnectionCost = ConnectionCost.UNKNOWN; 46 47 @SuppressLint("StaticFieldLeak") 48 private static NetworkChangeNotifier sInstance; 49 50 @VisibleForTesting NetworkChangeNotifier()51 protected NetworkChangeNotifier() { 52 mNativeChangeNotifiers = new ArrayList<Long>(); 53 mConnectionTypeObservers = new ObserverList<ConnectionTypeObserver>(); 54 } 55 56 /** 57 * Initializes the singleton once. 58 */ 59 @CalledByNative init()60 public static NetworkChangeNotifier init() { 61 if (sInstance == null) { 62 sInstance = new NetworkChangeNotifier(); 63 } 64 return sInstance; 65 } 66 isInitialized()67 public static boolean isInitialized() { 68 return sInstance != null; 69 } 70 71 @VisibleForTesting resetInstanceForTests()72 public static void resetInstanceForTests() { 73 sInstance = new NetworkChangeNotifier(); 74 } 75 76 @VisibleForTesting resetInstanceForTests(NetworkChangeNotifier notifier)77 public static void resetInstanceForTests(NetworkChangeNotifier notifier) { 78 sInstance = notifier; 79 } 80 81 @CalledByNative getCurrentConnectionType()82 public int getCurrentConnectionType() { 83 return mCurrentConnectionType; 84 } 85 86 @CalledByNative getCurrentConnectionSubtype()87 public int getCurrentConnectionSubtype() { 88 return mAutoDetector == null 89 ? ConnectionSubtype.SUBTYPE_UNKNOWN 90 : mAutoDetector.getCurrentNetworkState().getConnectionSubtype(); 91 } 92 93 @CalledByNative getCurrentConnectionCost()94 public int getCurrentConnectionCost() { 95 return mCurrentConnectionCost; 96 } 97 98 /** 99 * Returns NetID of device's current default connected network used for 100 * communication. Only available on Lollipop and newer releases and when 101 * auto-detection has been enabled, returns NetId.INVALID otherwise. 102 */ 103 @CalledByNative getCurrentDefaultNetId()104 public long getCurrentDefaultNetId() { 105 return mAutoDetector == null ? NetId.INVALID : mAutoDetector.getDefaultNetId(); 106 } 107 108 /** 109 * Returns an array of all of the device's currently connected 110 * networks and ConnectionTypes. Array elements are a repeated sequence of: 111 * NetID of network 112 * ConnectionType of network 113 * Only available on Lollipop and newer releases and when auto-detection has 114 * been enabled. 115 */ 116 @CalledByNative getCurrentNetworksAndTypes()117 public long[] getCurrentNetworksAndTypes() { 118 return mAutoDetector == null ? new long[0] : mAutoDetector.getNetworksAndTypes(); 119 } 120 121 /** 122 * Adds a native-side observer. 123 */ 124 @CalledByNative addNativeObserver(long nativeChangeNotifier)125 public void addNativeObserver(long nativeChangeNotifier) { 126 mNativeChangeNotifiers.add(nativeChangeNotifier); 127 } 128 129 /** 130 * Removes a native-side observer. 131 */ 132 @CalledByNative removeNativeObserver(long nativeChangeNotifier)133 public void removeNativeObserver(long nativeChangeNotifier) { 134 mNativeChangeNotifiers.remove(nativeChangeNotifier); 135 } 136 137 /** 138 * Returns {@code true} if NetworkCallback failed to register, indicating that network-specific 139 * callbacks will not be issued. 140 */ 141 @CalledByNative registerNetworkCallbackFailed()142 public boolean registerNetworkCallbackFailed() { 143 return mAutoDetector == null ? false : mAutoDetector.registerNetworkCallbackFailed(); 144 } 145 146 /** 147 * Returns the singleton instance. 148 */ getInstance()149 public static NetworkChangeNotifier getInstance() { 150 assert sInstance != null; 151 return sInstance; 152 } 153 154 /** 155 * Enables auto detection of the current network state based on notifications from the system. 156 * Note that passing true here requires the embedding app have the platform ACCESS_NETWORK_STATE 157 * permission. Also note that in this case the auto detection is enabled based on the status of 158 * the application (@see ApplicationStatus). 159 * Declare @CalledByNative only for testing. 160 * 161 * @param shouldAutoDetect true if the NetworkChangeNotifier should listen for system changes in 162 * network connectivity. 163 */ 164 @CalledByNative setAutoDetectConnectivityState(boolean shouldAutoDetect)165 public static void setAutoDetectConnectivityState(boolean shouldAutoDetect) { 166 getInstance().setAutoDetectConnectivityStateInternal( 167 shouldAutoDetect, new RegistrationPolicyApplicationStatus()); 168 } 169 170 /** 171 * Registers to always receive network change notifications no matter if 172 * the app is in the background or foreground. 173 * Note that in normal circumstances, chrome embedders should use 174 * {@code setAutoDetectConnectivityState} to listen to network changes only 175 * when the app is in the foreground, because network change observers 176 * might perform expensive work depending on the network connectivity. 177 */ registerToReceiveNotificationsAlways()178 public static void registerToReceiveNotificationsAlways() { 179 getInstance().setAutoDetectConnectivityStateInternal( 180 true, new RegistrationPolicyAlwaysRegister()); 181 } 182 183 /** 184 * Registers to receive network change notification based on the provided registration policy. 185 */ setAutoDetectConnectivityState( NetworkChangeNotifierAutoDetect.RegistrationPolicy policy)186 public static void setAutoDetectConnectivityState( 187 NetworkChangeNotifierAutoDetect.RegistrationPolicy policy) { 188 getInstance().setAutoDetectConnectivityStateInternal(true, policy); 189 } 190 destroyAutoDetector()191 private void destroyAutoDetector() { 192 if (mAutoDetector != null) { 193 mAutoDetector.destroy(); 194 mAutoDetector = null; 195 } 196 } 197 setAutoDetectConnectivityStateInternal( boolean shouldAutoDetect, NetworkChangeNotifierAutoDetect.RegistrationPolicy policy)198 private void setAutoDetectConnectivityStateInternal( 199 boolean shouldAutoDetect, NetworkChangeNotifierAutoDetect.RegistrationPolicy policy) { 200 if (shouldAutoDetect) { 201 if (mAutoDetector == null) { 202 mAutoDetector = new NetworkChangeNotifierAutoDetect( 203 new NetworkChangeNotifierAutoDetect.Observer() { 204 @Override 205 public void onConnectionTypeChanged(int newConnectionType) { 206 updateCurrentConnectionType(newConnectionType); 207 } 208 @Override 209 public void onConnectionCostChanged(int newConnectionCost) { 210 notifyObserversOfConnectionCostChange(newConnectionCost); 211 } 212 @Override 213 public void onConnectionSubtypeChanged(int newConnectionSubtype) { 214 notifyObserversOfConnectionSubtypeChange(newConnectionSubtype); 215 } 216 @Override 217 public void onNetworkConnect(long netId, int connectionType) { 218 notifyObserversOfNetworkConnect(netId, connectionType); 219 } 220 @Override 221 public void onNetworkSoonToDisconnect(long netId) { 222 notifyObserversOfNetworkSoonToDisconnect(netId); 223 } 224 @Override 225 public void onNetworkDisconnect(long netId) { 226 notifyObserversOfNetworkDisconnect(netId); 227 } 228 @Override 229 public void purgeActiveNetworkList(long[] activeNetIds) { 230 notifyObserversToPurgeActiveNetworkList(activeNetIds); 231 } 232 }, 233 policy); 234 final NetworkChangeNotifierAutoDetect.NetworkState networkState = 235 mAutoDetector.getCurrentNetworkState(); 236 updateCurrentConnectionType(networkState.getConnectionType()); 237 updateCurrentConnectionCost(networkState.getConnectionCost()); 238 notifyObserversOfConnectionSubtypeChange(networkState.getConnectionSubtype()); 239 } 240 } else { 241 destroyAutoDetector(); 242 } 243 } 244 245 /** 246 * For testing, updates the perceived network state when not auto-detecting changes to 247 * connectivity. 248 * 249 * @param networkAvailable True if the NetworkChangeNotifier should perceive a "connected" 250 * state, false implies "disconnected". 251 */ 252 @CalledByNative forceConnectivityState(boolean networkAvailable)253 public static void forceConnectivityState(boolean networkAvailable) { 254 setAutoDetectConnectivityState(false); 255 getInstance().forceConnectivityStateInternal(networkAvailable); 256 } 257 forceConnectivityStateInternal(boolean forceOnline)258 private void forceConnectivityStateInternal(boolean forceOnline) { 259 boolean connectionCurrentlyExists = 260 mCurrentConnectionType != ConnectionType.CONNECTION_NONE; 261 if (connectionCurrentlyExists != forceOnline) { 262 updateCurrentConnectionType(forceOnline ? ConnectionType.CONNECTION_UNKNOWN 263 : ConnectionType.CONNECTION_NONE); 264 notifyObserversOfConnectionSubtypeChange(forceOnline ? ConnectionSubtype.SUBTYPE_UNKNOWN 265 : ConnectionSubtype.SUBTYPE_NONE); 266 } 267 } 268 269 // For testing, pretend a network connected. 270 @CalledByNative fakeNetworkConnected(long netId, int connectionType)271 public static void fakeNetworkConnected(long netId, int connectionType) { 272 setAutoDetectConnectivityState(false); 273 getInstance().notifyObserversOfNetworkConnect(netId, connectionType); 274 } 275 276 // For testing, pretend a network will soon disconnect. 277 @CalledByNative fakeNetworkSoonToBeDisconnected(long netId)278 public static void fakeNetworkSoonToBeDisconnected(long netId) { 279 setAutoDetectConnectivityState(false); 280 getInstance().notifyObserversOfNetworkSoonToDisconnect(netId); 281 } 282 283 // For testing, pretend a network disconnected. 284 @CalledByNative fakeNetworkDisconnected(long netId)285 public static void fakeNetworkDisconnected(long netId) { 286 setAutoDetectConnectivityState(false); 287 getInstance().notifyObserversOfNetworkDisconnect(netId); 288 } 289 290 // For testing, pretend a network lists should be purged. 291 @CalledByNative fakePurgeActiveNetworkList(long[] activeNetIds)292 public static void fakePurgeActiveNetworkList(long[] activeNetIds) { 293 setAutoDetectConnectivityState(false); 294 getInstance().notifyObserversToPurgeActiveNetworkList(activeNetIds); 295 } 296 297 // For testing, pretend a default network changed. 298 @CalledByNative fakeDefaultNetwork(long netId, int connectionType)299 public static void fakeDefaultNetwork(long netId, int connectionType) { 300 setAutoDetectConnectivityState(false); 301 getInstance().notifyObserversOfConnectionTypeChange(connectionType, netId); 302 } 303 304 // For testing, pretend the connection cost has changed. 305 @CalledByNative 306 @VisibleForTesting fakeConnectionCostChanged(int connectionCost)307 public static void fakeConnectionCostChanged(int connectionCost) { 308 setAutoDetectConnectivityState(false); 309 getInstance().notifyObserversOfConnectionCostChange(connectionCost); 310 } 311 312 // For testing, pretend the connection subtype has changed. 313 @CalledByNative fakeConnectionSubtypeChanged(int connectionSubtype)314 public static void fakeConnectionSubtypeChanged(int connectionSubtype) { 315 setAutoDetectConnectivityState(false); 316 getInstance().notifyObserversOfConnectionSubtypeChange(connectionSubtype); 317 } 318 updateCurrentConnectionType(int newConnectionType)319 private void updateCurrentConnectionType(int newConnectionType) { 320 mCurrentConnectionType = newConnectionType; 321 notifyObserversOfConnectionTypeChange(newConnectionType); 322 } 323 324 /** 325 * Alerts all observers of a connection change. 326 */ notifyObserversOfConnectionTypeChange(int newConnectionType)327 void notifyObserversOfConnectionTypeChange(int newConnectionType) { 328 notifyObserversOfConnectionTypeChange(newConnectionType, getCurrentDefaultNetId()); 329 } 330 notifyObserversOfConnectionTypeChange(int newConnectionType, long defaultNetId)331 private void notifyObserversOfConnectionTypeChange(int newConnectionType, long defaultNetId) { 332 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 333 NetworkChangeNotifierJni.get().notifyConnectionTypeChanged(nativeChangeNotifier, 334 NetworkChangeNotifier.this, newConnectionType, defaultNetId); 335 } 336 for (ConnectionTypeObserver observer : mConnectionTypeObservers) { 337 observer.onConnectionTypeChanged(newConnectionType); 338 } 339 } 340 updateCurrentConnectionCost(int newConnectionCost)341 private void updateCurrentConnectionCost(int newConnectionCost) { 342 mCurrentConnectionCost = newConnectionCost; 343 notifyObserversOfConnectionCostChange(newConnectionCost); 344 } 345 346 /** 347 * Alerts all observers of a connection cost change. 348 */ notifyObserversOfConnectionCostChange(int newConnectionCost)349 void notifyObserversOfConnectionCostChange(int newConnectionCost) { 350 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 351 NetworkChangeNotifierJni.get().notifyConnectionCostChanged( 352 nativeChangeNotifier, NetworkChangeNotifier.this, newConnectionCost); 353 } 354 } 355 356 /** 357 * Alerts all observers of a bandwidth change. 358 */ notifyObserversOfConnectionSubtypeChange(int connectionSubtype)359 void notifyObserversOfConnectionSubtypeChange(int connectionSubtype) { 360 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 361 NetworkChangeNotifierJni.get().notifyMaxBandwidthChanged( 362 nativeChangeNotifier, NetworkChangeNotifier.this, connectionSubtype); 363 } 364 } 365 366 /** 367 * Alerts all observers of a network connect. 368 */ notifyObserversOfNetworkConnect(long netId, int connectionType)369 void notifyObserversOfNetworkConnect(long netId, int connectionType) { 370 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 371 NetworkChangeNotifierJni.get().notifyOfNetworkConnect( 372 nativeChangeNotifier, NetworkChangeNotifier.this, netId, connectionType); 373 } 374 } 375 376 /** 377 * Alerts all observers of a network soon to be disconnected. 378 */ notifyObserversOfNetworkSoonToDisconnect(long netId)379 void notifyObserversOfNetworkSoonToDisconnect(long netId) { 380 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 381 NetworkChangeNotifierJni.get().notifyOfNetworkSoonToDisconnect( 382 nativeChangeNotifier, NetworkChangeNotifier.this, netId); 383 } 384 } 385 386 /** 387 * Alerts all observers of a network disconnect. 388 */ notifyObserversOfNetworkDisconnect(long netId)389 void notifyObserversOfNetworkDisconnect(long netId) { 390 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 391 NetworkChangeNotifierJni.get().notifyOfNetworkDisconnect( 392 nativeChangeNotifier, NetworkChangeNotifier.this, netId); 393 } 394 } 395 396 /** 397 * Alerts all observers to purge cached lists of active networks, of any 398 * networks not in the accompanying list of active networks. This is 399 * issued if a period elapsed where disconnected notifications may have 400 * been missed, and acts to keep cached lists of active networks accurate. 401 */ notifyObserversToPurgeActiveNetworkList(long[] activeNetIds)402 void notifyObserversToPurgeActiveNetworkList(long[] activeNetIds) { 403 for (Long nativeChangeNotifier : mNativeChangeNotifiers) { 404 NetworkChangeNotifierJni.get().notifyPurgeActiveNetworkList( 405 nativeChangeNotifier, NetworkChangeNotifier.this, activeNetIds); 406 } 407 } 408 409 /** 410 * Adds an observer for any connection type changes. 411 */ addConnectionTypeObserver(ConnectionTypeObserver observer)412 public static void addConnectionTypeObserver(ConnectionTypeObserver observer) { 413 getInstance().addConnectionTypeObserverInternal(observer); 414 } 415 addConnectionTypeObserverInternal(ConnectionTypeObserver observer)416 private void addConnectionTypeObserverInternal(ConnectionTypeObserver observer) { 417 mConnectionTypeObservers.addObserver(observer); 418 } 419 420 /** 421 * Removes an observer for any connection type changes. 422 */ removeConnectionTypeObserver(ConnectionTypeObserver observer)423 public static void removeConnectionTypeObserver(ConnectionTypeObserver observer) { 424 getInstance().removeConnectionTypeObserverInternal(observer); 425 } 426 removeConnectionTypeObserverInternal(ConnectionTypeObserver observer)427 private void removeConnectionTypeObserverInternal(ConnectionTypeObserver observer) { 428 mConnectionTypeObservers.removeObserver(observer); 429 } 430 431 // For testing only. getAutoDetectorForTest()432 public static NetworkChangeNotifierAutoDetect getAutoDetectorForTest() { 433 return getInstance().mAutoDetector; 434 } 435 436 /** 437 * Checks if there currently is connectivity. 438 */ isOnline()439 public static boolean isOnline() { 440 int connectionType = getInstance().getCurrentConnectionType(); 441 return connectionType != ConnectionType.CONNECTION_NONE; 442 } 443 444 @NativeMethods 445 interface Natives { 446 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyConnectionTypeChanged(long nativePtr, NetworkChangeNotifier caller, int newConnectionType, long defaultNetId)447 void notifyConnectionTypeChanged(long nativePtr, NetworkChangeNotifier caller, 448 int newConnectionType, long defaultNetId); 449 450 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyConnectionCostChanged( long nativePtr, NetworkChangeNotifier caller, int newConnectionCost)451 void notifyConnectionCostChanged( 452 long nativePtr, NetworkChangeNotifier caller, int newConnectionCost); 453 454 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyMaxBandwidthChanged(long nativePtr, NetworkChangeNotifier caller, int subType)455 void notifyMaxBandwidthChanged(long nativePtr, NetworkChangeNotifier caller, int subType); 456 457 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyOfNetworkConnect( long nativePtr, NetworkChangeNotifier caller, long netId, int connectionType)458 void notifyOfNetworkConnect( 459 long nativePtr, NetworkChangeNotifier caller, long netId, int connectionType); 460 461 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyOfNetworkSoonToDisconnect( long nativePtr, NetworkChangeNotifier caller, long netId)462 void notifyOfNetworkSoonToDisconnect( 463 long nativePtr, NetworkChangeNotifier caller, long netId); 464 465 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyOfNetworkDisconnect(long nativePtr, NetworkChangeNotifier caller, long netId)466 void notifyOfNetworkDisconnect(long nativePtr, NetworkChangeNotifier caller, long netId); 467 468 @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") notifyPurgeActiveNetworkList( long nativePtr, NetworkChangeNotifier caller, long[] activeNetIds)469 void notifyPurgeActiveNetworkList( 470 long nativePtr, NetworkChangeNotifier caller, long[] activeNetIds); 471 } 472 } 473