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.content.Context;
14 import android.os.Build;
15 import android.support.annotation.Nullable;
16 import java.util.ArrayList;
17 import java.util.List;
18 import org.webrtc.NetworkChangeDetector;
19 
20 /**
21  * Borrowed from Chromium's
22  * src/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
23  *
24  * <p>Triggers updates to the underlying network state from OS networking events.
25  *
26  * <p>This class is thread-safe.
27  */
28 public class NetworkMonitor {
29   /**
30    * Alerted when the connection type of the network changes. The alert is fired on the UI thread.
31    */
32   public interface NetworkObserver {
onConnectionTypeChanged(NetworkChangeDetector.ConnectionType connectionType)33     public void onConnectionTypeChanged(NetworkChangeDetector.ConnectionType connectionType);
34   }
35 
36   private static final String TAG = "NetworkMonitor";
37 
38   // Lazy initialization holder class idiom for static fields.
39   private static class InstanceHolder {
40     // We are storing application context so it is okay.
41     static final NetworkMonitor instance = new NetworkMonitor();
42   }
43 
44   // Factory for creating NetworkChangeDetector.
45   private NetworkChangeDetectorFactory networkChangeDetectorFactory =
46       new NetworkChangeDetectorFactory() {
47         @Override
48         public NetworkChangeDetector create(
49             NetworkChangeDetector.Observer observer, Context context) {
50           return new NetworkMonitorAutoDetect(observer, context);
51         }
52       };
53 
54   // Native observers of the connection type changes.
55   private final ArrayList<Long> nativeNetworkObservers;
56   // Java observers of the connection type changes.
57   private final ArrayList<NetworkObserver> networkObservers;
58 
59   private final Object networkChangeDetectorLock = new Object();
60   // Object that detects the connection type changes and brings up mobile networks.
61   @Nullable private NetworkChangeDetector networkChangeDetector;
62   // Also guarded by autoDetectLock.
63   private int numObservers;
64 
65   private volatile NetworkChangeDetector.ConnectionType currentConnectionType;
66 
NetworkMonitor()67   private NetworkMonitor() {
68     nativeNetworkObservers = new ArrayList<Long>();
69     networkObservers = new ArrayList<NetworkObserver>();
70     numObservers = 0;
71     currentConnectionType = NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN;
72   }
73 
74   /**
75    * Set the factory that will be used to create the network change detector.
76    * Needs to be called before the monitoring is starts.
77    */
setNetworkChangeDetectorFactory(NetworkChangeDetectorFactory factory)78   public void setNetworkChangeDetectorFactory(NetworkChangeDetectorFactory factory) {
79     assertIsTrue(numObservers == 0);
80     this.networkChangeDetectorFactory = factory;
81   }
82 
83   // TODO(sakal): Remove once downstream dependencies have been updated.
84   @Deprecated
init(Context context)85   public static void init(Context context) {}
86 
87   /** Returns the singleton instance. This may be called from native or from Java code. */
88   @CalledByNative
getInstance()89   public static NetworkMonitor getInstance() {
90     return InstanceHolder.instance;
91   }
92 
assertIsTrue(boolean condition)93   private static void assertIsTrue(boolean condition) {
94     if (!condition) {
95       throw new AssertionError("Expected to be true");
96     }
97   }
98 
99   /**
100    * Enables auto detection of the network state change and brings up mobile networks for using
101    * multi-networking. This requires the embedding app have the platform ACCESS_NETWORK_STATE and
102    * CHANGE_NETWORK_STATE permission.
103    */
startMonitoring(Context applicationContext)104   public void startMonitoring(Context applicationContext) {
105     synchronized (networkChangeDetectorLock) {
106       ++numObservers;
107       if (networkChangeDetector == null) {
108         networkChangeDetector = createNetworkChangeDetector(applicationContext);
109       }
110       currentConnectionType = networkChangeDetector.getCurrentConnectionType();
111     }
112   }
113 
114   /** Deprecated, pass in application context in startMonitoring instead. */
115   @Deprecated
startMonitoring()116   public void startMonitoring() {
117     startMonitoring(ContextUtils.getApplicationContext());
118   }
119 
120   /**
121    * Enables auto detection of the network state change and brings up mobile networks for using
122    * multi-networking. This requires the embedding app have the platform ACCESS_NETWORK_STATE and
123    * CHANGE_NETWORK_STATE permission.
124    */
125   @CalledByNative
startMonitoring(@ullable Context applicationContext, long nativeObserver)126   private void startMonitoring(@Nullable Context applicationContext, long nativeObserver) {
127     Logging.d(TAG, "Start monitoring with native observer " + nativeObserver);
128 
129     startMonitoring(
130         applicationContext != null ? applicationContext : ContextUtils.getApplicationContext());
131     // The native observers expect a network list update after they call startMonitoring.
132     synchronized (nativeNetworkObservers) {
133       nativeNetworkObservers.add(nativeObserver);
134     }
135     updateObserverActiveNetworkList(nativeObserver);
136     // currentConnectionType was updated in startMonitoring().
137     // Need to notify the native observers here.
138     notifyObserversOfConnectionTypeChange(currentConnectionType);
139   }
140 
141   /**
142    * Stop network monitoring. If no one is monitoring networks, destroy and reset
143    * networkChangeDetector.
144    */
stopMonitoring()145   public void stopMonitoring() {
146     synchronized (networkChangeDetectorLock) {
147       if (--numObservers == 0) {
148         networkChangeDetector.destroy();
149         networkChangeDetector = null;
150       }
151     }
152   }
153 
154   @CalledByNative
stopMonitoring(long nativeObserver)155   private void stopMonitoring(long nativeObserver) {
156     Logging.d(TAG, "Stop monitoring with native observer " + nativeObserver);
157     stopMonitoring();
158     synchronized (nativeNetworkObservers) {
159       nativeNetworkObservers.remove(nativeObserver);
160     }
161   }
162 
163   // Returns true if network binding is supported on this platform.
164   @CalledByNative
networkBindingSupported()165   private boolean networkBindingSupported() {
166     synchronized (networkChangeDetectorLock) {
167       return networkChangeDetector != null && networkChangeDetector.supportNetworkCallback();
168     }
169   }
170 
171   @CalledByNative
androidSdkInt()172   private static int androidSdkInt() {
173     return Build.VERSION.SDK_INT;
174   }
175 
getCurrentConnectionType()176   private NetworkChangeDetector.ConnectionType getCurrentConnectionType() {
177     return currentConnectionType;
178   }
179 
createNetworkChangeDetector(Context appContext)180   private NetworkChangeDetector createNetworkChangeDetector(Context appContext) {
181     return networkChangeDetectorFactory.create(new NetworkChangeDetector.Observer() {
182       @Override
183       public void onConnectionTypeChanged(NetworkChangeDetector.ConnectionType newConnectionType) {
184         updateCurrentConnectionType(newConnectionType);
185       }
186 
187       @Override
188       public void onNetworkConnect(NetworkChangeDetector.NetworkInformation networkInfo) {
189         notifyObserversOfNetworkConnect(networkInfo);
190       }
191 
192       @Override
193       public void onNetworkDisconnect(long networkHandle) {
194         notifyObserversOfNetworkDisconnect(networkHandle);
195       }
196 
197       @Override
198       public void onNetworkPreference(
199           List<NetworkChangeDetector.ConnectionType> types, int preference) {
200         notifyObserversOfNetworkPreference(types, preference);
201       }
202     }, appContext);
203   }
204 
205   private void updateCurrentConnectionType(NetworkChangeDetector.ConnectionType newConnectionType) {
206     currentConnectionType = newConnectionType;
207     notifyObserversOfConnectionTypeChange(newConnectionType);
208   }
209 
210   /** Alerts all observers of a connection change. */
211   private void notifyObserversOfConnectionTypeChange(
212       NetworkChangeDetector.ConnectionType newConnectionType) {
213     List<Long> nativeObservers = getNativeNetworkObserversSync();
214     for (Long nativeObserver : nativeObservers) {
215       nativeNotifyConnectionTypeChanged(nativeObserver);
216     }
217     // This avoids calling external methods while locking on an object.
218     List<NetworkObserver> javaObservers;
219     synchronized (networkObservers) {
220       javaObservers = new ArrayList<>(networkObservers);
221     }
222     for (NetworkObserver observer : javaObservers) {
223       observer.onConnectionTypeChanged(newConnectionType);
224     }
225   }
226 
227   private void notifyObserversOfNetworkConnect(
228       NetworkChangeDetector.NetworkInformation networkInfo) {
229     List<Long> nativeObservers = getNativeNetworkObserversSync();
230     for (Long nativeObserver : nativeObservers) {
231       nativeNotifyOfNetworkConnect(nativeObserver, networkInfo);
232     }
233   }
234 
235   private void notifyObserversOfNetworkDisconnect(long networkHandle) {
236     List<Long> nativeObservers = getNativeNetworkObserversSync();
237     for (Long nativeObserver : nativeObservers) {
238       nativeNotifyOfNetworkDisconnect(nativeObserver, networkHandle);
239     }
240   }
241 
242   private void notifyObserversOfNetworkPreference(
243       List<NetworkChangeDetector.ConnectionType> types, int preference) {
244     List<Long> nativeObservers = getNativeNetworkObserversSync();
245     for (NetworkChangeDetector.ConnectionType type : types) {
246       for (Long nativeObserver : nativeObservers) {
247         nativeNotifyOfNetworkPreference(nativeObserver, type, preference);
248       }
249     }
250   }
251 
252   private void updateObserverActiveNetworkList(long nativeObserver) {
253     List<NetworkChangeDetector.NetworkInformation> networkInfoList;
254     synchronized (networkChangeDetectorLock) {
255       networkInfoList =
256           (networkChangeDetector == null) ? null : networkChangeDetector.getActiveNetworkList();
257     }
258     if (networkInfoList == null || networkInfoList.size() == 0) {
259       return;
260     }
261 
262     NetworkChangeDetector.NetworkInformation[] networkInfos =
263         new NetworkChangeDetector.NetworkInformation[networkInfoList.size()];
264     networkInfos = networkInfoList.toArray(networkInfos);
265     nativeNotifyOfActiveNetworkList(nativeObserver, networkInfos);
266   }
267 
268   private List<Long> getNativeNetworkObserversSync() {
269     synchronized (nativeNetworkObservers) {
270       return new ArrayList<>(nativeNetworkObservers);
271     }
272   }
273 
274   /**
275    * Adds an observer for any connection type changes.
276    *
277    * @deprecated Use getInstance(appContext).addObserver instead.
278    */
279   @Deprecated
280   public static void addNetworkObserver(NetworkObserver observer) {
281     getInstance().addObserver(observer);
282   }
283 
284   public void addObserver(NetworkObserver observer) {
285     synchronized (networkObservers) {
286       networkObservers.add(observer);
287     }
288   }
289 
290   /**
291    * Removes an observer for any connection type changes.
292    *
293    * @deprecated Use getInstance(appContext).removeObserver instead.
294    */
295   @Deprecated
296   public static void removeNetworkObserver(NetworkObserver observer) {
297     getInstance().removeObserver(observer);
298   }
299 
300   public void removeObserver(NetworkObserver observer) {
301     synchronized (networkObservers) {
302       networkObservers.remove(observer);
303     }
304   }
305 
306   /** Checks if there currently is connectivity. */
307   public static boolean isOnline() {
308     NetworkChangeDetector.ConnectionType connectionType = getInstance().getCurrentConnectionType();
309     return connectionType != NetworkChangeDetector.ConnectionType.CONNECTION_NONE;
310   }
311 
312   private native void nativeNotifyConnectionTypeChanged(long nativeAndroidNetworkMonitor);
313 
314   private native void nativeNotifyOfNetworkConnect(
315       long nativeAndroidNetworkMonitor, NetworkChangeDetector.NetworkInformation networkInfo);
316 
317   private native void nativeNotifyOfNetworkDisconnect(
318       long nativeAndroidNetworkMonitor, long networkHandle);
319 
320   private native void nativeNotifyOfActiveNetworkList(
321       long nativeAndroidNetworkMonitor, NetworkChangeDetector.NetworkInformation[] networkInfos);
322 
323   private native void nativeNotifyOfNetworkPreference(
324       long nativeAndroidNetworkMonitor, NetworkChangeDetector.ConnectionType type, int preference);
325 
326   // For testing only.
327   @Nullable
328   NetworkChangeDetector getNetworkChangeDetector() {
329     synchronized (networkChangeDetectorLock) {
330       return networkChangeDetector;
331     }
332   }
333 
334   // For testing only.
335   int getNumObservers() {
336     synchronized (networkChangeDetectorLock) {
337       return numObservers;
338     }
339   }
340 
341   // For testing only.
342   static NetworkMonitorAutoDetect createAndSetAutoDetectForTest(Context context) {
343     NetworkMonitor networkMonitor = getInstance();
344     NetworkChangeDetector networkChangeDetector =
345         networkMonitor.createNetworkChangeDetector(context);
346     networkMonitor.networkChangeDetector = networkChangeDetector;
347     return (NetworkMonitorAutoDetect) networkChangeDetector;
348   }
349 }
350