| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.net; |
| |
| import android.annotation.SuppressLint; |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.os.Build; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.ObserverList; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.annotations.NativeClassQualifiedName; |
| import org.chromium.base.compat.ApiHelperForM; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Triggers updates to the underlying network state in Chrome. |
| * |
| * By default, connectivity is assumed and changes must be pushed from the embedder via the |
| * forceConnectivityState function. |
| * Embedders may choose to have this class auto-detect changes in network connectivity by invoking |
| * the setAutoDetectConnectivityState function. |
| * |
| * WARNING: This class is not thread-safe. |
| */ |
| @JNINamespace("net") |
| public class NetworkChangeNotifier { |
| /** |
| * Alerted when the connection type of the network changes. |
| * The alert is fired on the UI thread. |
| */ |
| public interface ConnectionTypeObserver { |
| public void onConnectionTypeChanged(int connectionType); |
| } |
| |
| private final ArrayList<Long> mNativeChangeNotifiers; |
| private final ObserverList<ConnectionTypeObserver> mConnectionTypeObservers; |
| private final ConnectivityManager mConnectivityManager; |
| private NetworkChangeNotifierAutoDetect mAutoDetector; |
| // Last value broadcast via ConnectionTypeChange signal. |
| private int mCurrentConnectionType = ConnectionType.CONNECTION_UNKNOWN; |
| |
| @SuppressLint("StaticFieldLeak") |
| private static NetworkChangeNotifier sInstance; |
| |
| @VisibleForTesting |
| protected NetworkChangeNotifier() { |
| mNativeChangeNotifiers = new ArrayList<Long>(); |
| mConnectionTypeObservers = new ObserverList<ConnectionTypeObserver>(); |
| mConnectivityManager = |
| (ConnectivityManager) ContextUtils.getApplicationContext().getSystemService( |
| Context.CONNECTIVITY_SERVICE); |
| } |
| |
| /** |
| * Initializes the singleton once. |
| */ |
| @CalledByNative |
| public static NetworkChangeNotifier init() { |
| if (sInstance == null) { |
| sInstance = new NetworkChangeNotifier(); |
| } |
| return sInstance; |
| } |
| |
| public static boolean isInitialized() { |
| return sInstance != null; |
| } |
| |
| static void resetInstanceForTests(NetworkChangeNotifier notifier) { |
| sInstance = notifier; |
| } |
| |
| @CalledByNative |
| public int getCurrentConnectionType() { |
| return mCurrentConnectionType; |
| } |
| |
| @CalledByNative |
| public int getCurrentConnectionSubtype() { |
| return mAutoDetector == null |
| ? ConnectionSubtype.SUBTYPE_UNKNOWN |
| : mAutoDetector.getCurrentNetworkState().getConnectionSubtype(); |
| } |
| |
| /** |
| * Returns NetID of device's current default connected network used for |
| * communication. Only available on Lollipop and newer releases and when |
| * auto-detection has been enabled, returns NetId.INVALID otherwise. |
| */ |
| @CalledByNative |
| public long getCurrentDefaultNetId() { |
| return mAutoDetector == null ? NetId.INVALID : mAutoDetector.getDefaultNetId(); |
| } |
| |
| /** |
| * Returns an array of all of the device's currently connected |
| * networks and ConnectionTypes. Array elements are a repeated sequence of: |
| * NetID of network |
| * ConnectionType of network |
| * Only available on Lollipop and newer releases and when auto-detection has |
| * been enabled. |
| */ |
| @CalledByNative |
| public long[] getCurrentNetworksAndTypes() { |
| return mAutoDetector == null ? new long[0] : mAutoDetector.getNetworksAndTypes(); |
| } |
| |
| /** |
| * Adds a native-side observer. |
| */ |
| @CalledByNative |
| public void addNativeObserver(long nativeChangeNotifier) { |
| mNativeChangeNotifiers.add(nativeChangeNotifier); |
| } |
| |
| /** |
| * Removes a native-side observer. |
| */ |
| @CalledByNative |
| public void removeNativeObserver(long nativeChangeNotifier) { |
| mNativeChangeNotifiers.remove(nativeChangeNotifier); |
| } |
| |
| /** |
| * Returns {@code true} if NetworkCallback failed to register, indicating that network-specific |
| * callbacks will not be issued. |
| */ |
| @CalledByNative |
| public boolean registerNetworkCallbackFailed() { |
| return mAutoDetector == null ? false : mAutoDetector.registerNetworkCallbackFailed(); |
| } |
| |
| /** |
| * Returns the singleton instance. |
| */ |
| public static NetworkChangeNotifier getInstance() { |
| assert sInstance != null; |
| return sInstance; |
| } |
| |
| /** |
| * Enables auto detection of the current network state based on notifications from the system. |
| * Note that passing true here requires the embedding app have the platform ACCESS_NETWORK_STATE |
| * permission. Also note that in this case the auto detection is enabled based on the status of |
| * the application (@see ApplicationStatus). |
| * |
| * @param shouldAutoDetect true if the NetworkChangeNotifier should listen for system changes in |
| * network connectivity. |
| */ |
| public static void setAutoDetectConnectivityState(boolean shouldAutoDetect) { |
| getInstance().setAutoDetectConnectivityStateInternal( |
| shouldAutoDetect, new RegistrationPolicyApplicationStatus()); |
| } |
| |
| /** |
| * Registers to always receive network change notifications no matter if |
| * the app is in the background or foreground. |
| * Note that in normal circumstances, chrome embedders should use |
| * {@code setAutoDetectConnectivityState} to listen to network changes only |
| * when the app is in the foreground, because network change observers |
| * might perform expensive work depending on the network connectivity. |
| */ |
| public static void registerToReceiveNotificationsAlways() { |
| getInstance().setAutoDetectConnectivityStateInternal( |
| true, new RegistrationPolicyAlwaysRegister()); |
| } |
| |
| /** |
| * Registers to receive network change notification based on the provided registration policy. |
| */ |
| public static void setAutoDetectConnectivityState( |
| NetworkChangeNotifierAutoDetect.RegistrationPolicy policy) { |
| getInstance().setAutoDetectConnectivityStateInternal(true, policy); |
| } |
| |
| private void destroyAutoDetector() { |
| if (mAutoDetector != null) { |
| mAutoDetector.destroy(); |
| mAutoDetector = null; |
| } |
| } |
| |
| private void setAutoDetectConnectivityStateInternal( |
| boolean shouldAutoDetect, NetworkChangeNotifierAutoDetect.RegistrationPolicy policy) { |
| if (shouldAutoDetect) { |
| if (mAutoDetector == null) { |
| mAutoDetector = new NetworkChangeNotifierAutoDetect( |
| new NetworkChangeNotifierAutoDetect.Observer() { |
| @Override |
| public void onConnectionTypeChanged(int newConnectionType) { |
| updateCurrentConnectionType(newConnectionType); |
| } |
| @Override |
| public void onConnectionSubtypeChanged(int newConnectionSubtype) { |
| notifyObserversOfConnectionSubtypeChange(newConnectionSubtype); |
| } |
| @Override |
| public void onNetworkConnect(long netId, int connectionType) { |
| notifyObserversOfNetworkConnect(netId, connectionType); |
| } |
| @Override |
| public void onNetworkSoonToDisconnect(long netId) { |
| notifyObserversOfNetworkSoonToDisconnect(netId); |
| } |
| @Override |
| public void onNetworkDisconnect(long netId) { |
| notifyObserversOfNetworkDisconnect(netId); |
| } |
| @Override |
| public void purgeActiveNetworkList(long[] activeNetIds) { |
| notifyObserversToPurgeActiveNetworkList(activeNetIds); |
| } |
| }, |
| policy); |
| final NetworkChangeNotifierAutoDetect.NetworkState networkState = |
| mAutoDetector.getCurrentNetworkState(); |
| updateCurrentConnectionType(networkState.getConnectionType()); |
| notifyObserversOfConnectionSubtypeChange(networkState.getConnectionSubtype()); |
| } |
| } else { |
| destroyAutoDetector(); |
| } |
| } |
| |
| /** |
| * For testing, updates the perceived network state when not auto-detecting changes to |
| * connectivity. |
| * |
| * @param networkAvailable True if the NetworkChangeNotifier should perceive a "connected" |
| * state, false implies "disconnected". |
| */ |
| @CalledByNative |
| public static void forceConnectivityState(boolean networkAvailable) { |
| setAutoDetectConnectivityState(false); |
| getInstance().forceConnectivityStateInternal(networkAvailable); |
| } |
| |
| private void forceConnectivityStateInternal(boolean forceOnline) { |
| boolean connectionCurrentlyExists = |
| mCurrentConnectionType != ConnectionType.CONNECTION_NONE; |
| if (connectionCurrentlyExists != forceOnline) { |
| updateCurrentConnectionType(forceOnline ? ConnectionType.CONNECTION_UNKNOWN |
| : ConnectionType.CONNECTION_NONE); |
| notifyObserversOfConnectionSubtypeChange(forceOnline ? ConnectionSubtype.SUBTYPE_UNKNOWN |
| : ConnectionSubtype.SUBTYPE_NONE); |
| } |
| } |
| |
| // For testing, pretend a network connected. |
| @CalledByNative |
| public static void fakeNetworkConnected(long netId, int connectionType) { |
| setAutoDetectConnectivityState(false); |
| getInstance().notifyObserversOfNetworkConnect(netId, connectionType); |
| } |
| |
| // For testing, pretend a network will soon disconnect. |
| @CalledByNative |
| public static void fakeNetworkSoonToBeDisconnected(long netId) { |
| setAutoDetectConnectivityState(false); |
| getInstance().notifyObserversOfNetworkSoonToDisconnect(netId); |
| } |
| |
| // For testing, pretend a network disconnected. |
| @CalledByNative |
| public static void fakeNetworkDisconnected(long netId) { |
| setAutoDetectConnectivityState(false); |
| getInstance().notifyObserversOfNetworkDisconnect(netId); |
| } |
| |
| // For testing, pretend a network lists should be purged. |
| @CalledByNative |
| public static void fakePurgeActiveNetworkList(long[] activeNetIds) { |
| setAutoDetectConnectivityState(false); |
| getInstance().notifyObserversToPurgeActiveNetworkList(activeNetIds); |
| } |
| |
| // For testing, pretend a default network changed. |
| @CalledByNative |
| public static void fakeDefaultNetwork(long netId, int connectionType) { |
| setAutoDetectConnectivityState(false); |
| getInstance().notifyObserversOfConnectionTypeChange(connectionType, netId); |
| } |
| |
| // For testing, pretend the connection subtype has changed. |
| @CalledByNative |
| public static void fakeConnectionSubtypeChanged(int connectionSubtype) { |
| setAutoDetectConnectivityState(false); |
| getInstance().notifyObserversOfConnectionSubtypeChange(connectionSubtype); |
| } |
| |
| private void updateCurrentConnectionType(int newConnectionType) { |
| mCurrentConnectionType = newConnectionType; |
| notifyObserversOfConnectionTypeChange(newConnectionType); |
| } |
| |
| /** |
| * Alerts all observers of a connection change. |
| */ |
| void notifyObserversOfConnectionTypeChange(int newConnectionType) { |
| notifyObserversOfConnectionTypeChange(newConnectionType, getCurrentDefaultNetId()); |
| } |
| |
| private void notifyObserversOfConnectionTypeChange(int newConnectionType, long defaultNetId) { |
| for (Long nativeChangeNotifier : mNativeChangeNotifiers) { |
| nativeNotifyConnectionTypeChanged( |
| nativeChangeNotifier, newConnectionType, defaultNetId); |
| } |
| for (ConnectionTypeObserver observer : mConnectionTypeObservers) { |
| observer.onConnectionTypeChanged(newConnectionType); |
| } |
| } |
| |
| /** |
| * Alerts all observers of a bandwidth change. |
| */ |
| void notifyObserversOfConnectionSubtypeChange(int connectionSubtype) { |
| for (Long nativeChangeNotifier : mNativeChangeNotifiers) { |
| nativeNotifyMaxBandwidthChanged(nativeChangeNotifier, connectionSubtype); |
| } |
| } |
| |
| /** |
| * Alerts all observers of a network connect. |
| */ |
| void notifyObserversOfNetworkConnect(long netId, int connectionType) { |
| for (Long nativeChangeNotifier : mNativeChangeNotifiers) { |
| nativeNotifyOfNetworkConnect(nativeChangeNotifier, netId, connectionType); |
| } |
| } |
| |
| /** |
| * Alerts all observers of a network soon to be disconnected. |
| */ |
| void notifyObserversOfNetworkSoonToDisconnect(long netId) { |
| for (Long nativeChangeNotifier : mNativeChangeNotifiers) { |
| nativeNotifyOfNetworkSoonToDisconnect(nativeChangeNotifier, netId); |
| } |
| } |
| |
| /** |
| * Alerts all observers of a network disconnect. |
| */ |
| void notifyObserversOfNetworkDisconnect(long netId) { |
| for (Long nativeChangeNotifier : mNativeChangeNotifiers) { |
| nativeNotifyOfNetworkDisconnect(nativeChangeNotifier, netId); |
| } |
| } |
| |
| /** |
| * Alerts all observers to purge cached lists of active networks, of any |
| * networks not in the accompanying list of active networks. This is |
| * issued if a period elapsed where disconnected notifications may have |
| * been missed, and acts to keep cached lists of active networks accurate. |
| */ |
| void notifyObserversToPurgeActiveNetworkList(long[] activeNetIds) { |
| for (Long nativeChangeNotifier : mNativeChangeNotifiers) { |
| nativeNotifyPurgeActiveNetworkList(nativeChangeNotifier, activeNetIds); |
| } |
| } |
| |
| /** |
| * Adds an observer for any connection type changes. |
| */ |
| public static void addConnectionTypeObserver(ConnectionTypeObserver observer) { |
| getInstance().addConnectionTypeObserverInternal(observer); |
| } |
| |
| private void addConnectionTypeObserverInternal(ConnectionTypeObserver observer) { |
| mConnectionTypeObservers.addObserver(observer); |
| } |
| |
| /** |
| * Removes an observer for any connection type changes. |
| */ |
| public static void removeConnectionTypeObserver(ConnectionTypeObserver observer) { |
| getInstance().removeConnectionTypeObserverInternal(observer); |
| } |
| |
| private void removeConnectionTypeObserverInternal(ConnectionTypeObserver observer) { |
| mConnectionTypeObservers.removeObserver(observer); |
| } |
| |
| /** |
| * Is the process bound to a network? |
| */ |
| private boolean isProcessBoundToNetworkInternal() { |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { |
| return false; |
| } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
| @SuppressWarnings("deprecation") |
| boolean returnValue = ConnectivityManager.getProcessDefaultNetwork() != null; |
| return returnValue; |
| } else { |
| return ApiHelperForM.getBoundNetworkForProcess(mConnectivityManager) != null; |
| } |
| } |
| |
| /** |
| * Is the process bound to a network? |
| */ |
| @CalledByNative |
| public static boolean isProcessBoundToNetwork() { |
| return getInstance().isProcessBoundToNetworkInternal(); |
| } |
| |
| @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") |
| private native void nativeNotifyConnectionTypeChanged( |
| long nativePtr, int newConnectionType, long defaultNetId); |
| |
| @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") |
| private native void nativeNotifyMaxBandwidthChanged(long nativePtr, int subType); |
| |
| @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") |
| private native void nativeNotifyOfNetworkConnect( |
| long nativePtr, long netId, int connectionType); |
| |
| @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") |
| private native void nativeNotifyOfNetworkSoonToDisconnect(long nativePtr, long netId); |
| |
| @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") |
| private native void nativeNotifyOfNetworkDisconnect(long nativePtr, long netId); |
| |
| @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") |
| private native void nativeNotifyPurgeActiveNetworkList(long nativePtr, long[] activeNetIds); |
| |
| // For testing only. |
| public static NetworkChangeNotifierAutoDetect getAutoDetectorForTest() { |
| return getInstance().mAutoDetector; |
| } |
| |
| /** |
| * Checks if there currently is connectivity. |
| */ |
| public static boolean isOnline() { |
| int connectionType = getInstance().getCurrentConnectionType(); |
| return connectionType != ConnectionType.CONNECTION_NONE; |
| } |
| } |