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