6
6
import android .content .Intent ;
7
7
import android .content .IntentFilter ;
8
8
import android .net .ConnectivityManager ;
9
+ import android .net .ConnectivityManager .NetworkCallback ;
10
+ import android .net .Network ;
9
11
import android .net .NetworkInfo ;
12
+ import android .os .Build ;
13
+ import android .os .Build .VERSION_CODES ;
10
14
import android .util .Log ;
11
15
import androidx .annotation .GuardedBy ;
12
16
import androidx .annotation .NonNull ;
17
+ import androidx .annotation .RequiresApi ;
13
18
import androidx .annotation .VisibleForTesting ;
14
19
import com .bumptech .glide .manager .ConnectivityMonitor .ConnectivityListener ;
15
- import com .bumptech .glide .util .Preconditions ;
20
+ import com .bumptech .glide .util .GlideSuppliers ;
21
+ import com .bumptech .glide .util .GlideSuppliers .GlideSupplier ;
16
22
import com .bumptech .glide .util .Synthetic ;
23
+ import com .bumptech .glide .util .Util ;
17
24
import java .util .ArrayList ;
18
25
import java .util .HashSet ;
19
26
import java .util .List ;
22
29
/** Uses {@link android.net.ConnectivityManager} to identify connectivity changes. */
23
30
final class SingletonConnectivityReceiver {
24
31
private static volatile SingletonConnectivityReceiver instance ;
25
- @ Synthetic static final String TAG = "ConnectivityMonitor" ;
26
- // Only accessed on the main thread.
27
- @ Synthetic boolean isConnected ;
28
-
29
- private final BroadcastReceiver connectivityReceiver =
30
- new BroadcastReceiver () {
31
- @ Override
32
- public void onReceive (@ NonNull Context context , Intent intent ) {
33
- List <ConnectivityListener > listenersToNotify = null ;
34
- boolean wasConnected = isConnected ;
35
- isConnected = isConnected (context );
36
- if (wasConnected != isConnected ) {
37
- if (Log .isLoggable (TAG , Log .DEBUG )) {
38
- Log .d (TAG , "connectivity changed, isConnected: " + isConnected );
39
- }
40
-
41
- synchronized (SingletonConnectivityReceiver .this ) {
42
- listenersToNotify = new ArrayList <>(listeners );
43
- }
44
- }
45
- // Make sure that we do not hold our lock while calling our listener. Otherwise we could
46
- // deadlock where our listener acquires its lock, then tries to acquire ours elsewhere and
47
- // then here we acquire our lock and try to acquire theirs.
48
- // The consequence of this is that we may notify a listener after it has been
49
- // unregistered in a few specific (unlikely) scenarios. That appears to be safe and is
50
- // documented in the unregister method.
51
- if (listenersToNotify != null ) {
52
- for (ConnectivityListener listener : listenersToNotify ) {
53
- listener .onConnectivityChanged (isConnected );
54
- }
55
- }
56
- }
57
- };
32
+ private static final String TAG = "ConnectivityMonitor" ;
58
33
59
- private final Context context ;
34
+ private final FrameworkConnectivityMonitor frameworkConnectivityMonitor ;
60
35
61
36
@ GuardedBy ("this" )
62
37
@ Synthetic
@@ -69,7 +44,7 @@ static SingletonConnectivityReceiver get(@NonNull Context context) {
69
44
if (instance == null ) {
70
45
synchronized (SingletonConnectivityReceiver .class ) {
71
46
if (instance == null ) {
72
- instance = new SingletonConnectivityReceiver (context );
47
+ instance = new SingletonConnectivityReceiver (context . getApplicationContext () );
73
48
}
74
49
}
75
50
}
@@ -81,8 +56,34 @@ static void reset() {
81
56
instance = null ;
82
57
}
83
58
84
- private SingletonConnectivityReceiver (@ NonNull Context context ) {
85
- this .context = context .getApplicationContext ();
59
+ private SingletonConnectivityReceiver (final @ NonNull Context context ) {
60
+ GlideSupplier <ConnectivityManager > connectivityManager =
61
+ GlideSuppliers .memorize (
62
+ new GlideSupplier <ConnectivityManager >() {
63
+ @ Override
64
+ public ConnectivityManager get () {
65
+ return (ConnectivityManager ) context .getSystemService (Context .CONNECTIVITY_SERVICE );
66
+ }
67
+ });
68
+ ConnectivityListener connectivityListener =
69
+ new ConnectivityListener () {
70
+ @ Override
71
+ public void onConnectivityChanged (boolean isConnected ) {
72
+ List <ConnectivityListener > toNotify ;
73
+ synchronized (SingletonConnectivityReceiver .this ) {
74
+ toNotify = new ArrayList <>(listeners );
75
+ }
76
+ for (ConnectivityListener listener : toNotify ) {
77
+ listener .onConnectivityChanged (isConnected );
78
+ }
79
+ }
80
+ };
81
+
82
+ frameworkConnectivityMonitor =
83
+ Build .VERSION .SDK_INT >= Build .VERSION_CODES .N
84
+ ? new FrameworkConnectivityMonitorPostApi24 (connectivityManager , connectivityListener )
85
+ : new FrameworkConnectivityMonitorPreApi24 (
86
+ context , connectivityManager , connectivityListener );
86
87
}
87
88
88
89
synchronized void register (ConnectivityListener listener ) {
@@ -106,18 +107,7 @@ private void maybeRegisterReceiver() {
106
107
if (isRegistered || listeners .isEmpty ()) {
107
108
return ;
108
109
}
109
- isConnected = isConnected (context );
110
- try {
111
- // See #1405
112
- context .registerReceiver (
113
- connectivityReceiver , new IntentFilter (ConnectivityManager .CONNECTIVITY_ACTION ));
114
- isRegistered = true ;
115
- } catch (SecurityException e ) {
116
- // See #1417, registering the receiver can throw SecurityException.
117
- if (Log .isLoggable (TAG , Log .WARN )) {
118
- Log .w (TAG , "Failed to register" , e );
119
- }
120
- }
110
+ isRegistered = frameworkConnectivityMonitor .register ();
121
111
}
122
112
123
113
@ GuardedBy ("this" )
@@ -126,31 +116,167 @@ private void maybeUnregisterReceiver() {
126
116
return ;
127
117
}
128
118
129
- context . unregisterReceiver ( connectivityReceiver );
119
+ frameworkConnectivityMonitor . unregister ( );
130
120
isRegistered = false ;
131
121
}
132
122
133
- @ SuppressWarnings ("WeakerAccess" )
134
- @ Synthetic
135
- // Permissions are checked in the factory instead.
136
- @ SuppressLint ("MissingPermission" )
137
- boolean isConnected (@ NonNull Context context ) {
138
- ConnectivityManager connectivityManager =
139
- Preconditions .checkNotNull (
140
- (ConnectivityManager ) context .getSystemService (Context .CONNECTIVITY_SERVICE ));
141
- NetworkInfo networkInfo ;
142
- try {
143
- networkInfo = connectivityManager .getActiveNetworkInfo ();
144
- } catch (RuntimeException e ) {
145
- // #1405 shows that this throws a SecurityException.
146
- // b/70869360 shows that this throws NullPointerException on APIs 22, 23, and 24.
147
- // b/70869360 also shows that this throws RuntimeException on API 24 and 25.
148
- if (Log .isLoggable (TAG , Log .WARN )) {
149
- Log .w (TAG , "Failed to determine connectivity status when connectivity changed" , e );
123
+ private interface FrameworkConnectivityMonitor {
124
+ boolean register ();
125
+
126
+ void unregister ();
127
+ }
128
+
129
+ @ RequiresApi (VERSION_CODES .N )
130
+ private static final class FrameworkConnectivityMonitorPostApi24
131
+ implements FrameworkConnectivityMonitor {
132
+
133
+ @ Synthetic boolean isConnected ;
134
+ @ Synthetic final ConnectivityListener listener ;
135
+ private final GlideSupplier <ConnectivityManager > connectivityManager ;
136
+ private final NetworkCallback networkCallback =
137
+ new NetworkCallback () {
138
+ @ Override
139
+ public void onAvailable (@ NonNull Network network ) {
140
+ postOnConnectivityChange (true );
141
+ }
142
+
143
+ @ Override
144
+ public void onLost (@ NonNull Network network ) {
145
+ postOnConnectivityChange (false );
146
+ }
147
+
148
+ private void postOnConnectivityChange (final boolean newState ) {
149
+ // We could use registerDefaultNetworkCallback with a Handler, but that's only available
150
+ // on API 26, instead of API 24. We can mimic the same behavior here manually by
151
+ // posting to the UI thread. All calls have to be posted to make sure that we retain the
152
+ // original order. Otherwise a call on a background thread, followed by a call on the UI
153
+ // thread could result in the first call running second.
154
+ Util .postOnUiThread (
155
+ new Runnable () {
156
+ @ Override
157
+ public void run () {
158
+ onConnectivityChange (newState );
159
+ }
160
+ });
161
+ }
162
+
163
+ @ Synthetic
164
+ void onConnectivityChange (boolean newState ) {
165
+ // See b/201425456.
166
+ Util .assertMainThread ();
167
+
168
+ boolean wasConnected = isConnected ;
169
+ isConnected = newState ;
170
+ if (wasConnected != newState ) {
171
+ listener .onConnectivityChanged (newState );
172
+ }
173
+ }
174
+ };
175
+
176
+ FrameworkConnectivityMonitorPostApi24 (
177
+ GlideSupplier <ConnectivityManager > connectivityManager , ConnectivityListener listener ) {
178
+ this .connectivityManager = connectivityManager ;
179
+ this .listener = listener ;
180
+ }
181
+
182
+ // Permissions are checked in the factory instead.
183
+ @ SuppressLint ("MissingPermission" )
184
+ @ Override
185
+ public boolean register () {
186
+ isConnected = connectivityManager .get ().getActiveNetwork () != null ;
187
+ try {
188
+ connectivityManager .get ().registerDefaultNetworkCallback (networkCallback );
189
+ return true ;
190
+ // See b/201664814, b/204226444: At least TooManyRequestsException is not public and
191
+ // doesn't extend from any subclass :/.
192
+ } catch (RuntimeException e ) {
193
+ if (Log .isLoggable (TAG , Log .WARN )) {
194
+ Log .w (TAG , "Failed to register callback" , e );
195
+ }
196
+ return false ;
197
+ }
198
+ }
199
+
200
+ @ Override
201
+ public void unregister () {
202
+ connectivityManager .get ().unregisterNetworkCallback (networkCallback );
203
+ }
204
+ }
205
+
206
+ private static final class FrameworkConnectivityMonitorPreApi24
207
+ implements FrameworkConnectivityMonitor {
208
+ private final Context context ;
209
+ @ Synthetic final ConnectivityListener listener ;
210
+ private final GlideSupplier <ConnectivityManager > connectivityManager ;
211
+ @ Synthetic boolean isConnected ;
212
+
213
+ private final BroadcastReceiver connectivityReceiver =
214
+ new BroadcastReceiver () {
215
+ @ Override
216
+ public void onReceive (@ NonNull Context context , Intent intent ) {
217
+ boolean wasConnected = isConnected ;
218
+ isConnected = isConnected ();
219
+ if (wasConnected != isConnected ) {
220
+ if (Log .isLoggable (TAG , Log .DEBUG )) {
221
+ Log .d (TAG , "connectivity changed, isConnected: " + isConnected );
222
+ }
223
+
224
+ listener .onConnectivityChanged (isConnected );
225
+ }
226
+ }
227
+ };
228
+
229
+ FrameworkConnectivityMonitorPreApi24 (
230
+ Context context ,
231
+ GlideSupplier <ConnectivityManager > connectivityManager ,
232
+ ConnectivityListener listener ) {
233
+ this .context = context .getApplicationContext ();
234
+ this .connectivityManager = connectivityManager ;
235
+ this .listener = listener ;
236
+ }
237
+
238
+ @ Override
239
+ public boolean register () {
240
+ // Initialize isConnected so that we notice the first time around when there's a broadcast.
241
+ isConnected = isConnected ();
242
+ try {
243
+ // See #1405
244
+ context .registerReceiver (
245
+ connectivityReceiver , new IntentFilter (ConnectivityManager .CONNECTIVITY_ACTION ));
246
+ return true ;
247
+ } catch (SecurityException e ) {
248
+ // See #1417, registering the receiver can throw SecurityException.
249
+ if (Log .isLoggable (TAG , Log .WARN )) {
250
+ Log .w (TAG , "Failed to register" , e );
251
+ }
252
+ return false ;
253
+ }
254
+ }
255
+
256
+ @ Override
257
+ public void unregister () {
258
+ context .unregisterReceiver (connectivityReceiver );
259
+ }
260
+
261
+ @ SuppressWarnings ("WeakerAccess" )
262
+ @ Synthetic
263
+ // Permissions are checked in the factory instead.
264
+ @ SuppressLint ("MissingPermission" )
265
+ boolean isConnected () {
266
+ NetworkInfo networkInfo ;
267
+ try {
268
+ networkInfo = connectivityManager .get ().getActiveNetworkInfo ();
269
+ } catch (RuntimeException e ) {
270
+ // #1405 shows that this throws a SecurityException.
271
+ // b/70869360 shows that this throws NullPointerException on APIs 22, 23, and 24.
272
+ // b/70869360 also shows that this throws RuntimeException on API 24 and 25.
273
+ if (Log .isLoggable (TAG , Log .WARN )) {
274
+ Log .w (TAG , "Failed to determine connectivity status when connectivity changed" , e );
275
+ }
276
+ // Default to true;
277
+ return true ;
150
278
}
151
- // Default to true;
152
- return true ;
279
+ return networkInfo != null && networkInfo .isConnected ();
153
280
}
154
- return networkInfo != null && networkInfo .isConnected ();
155
281
}
156
282
}
0 commit comments