1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net; 18 19 import static android.net.ConnectivityManager.TYPE_NONE; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; 22 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; 23 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; 24 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 25 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; 26 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; 27 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; 28 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; 29 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; 30 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; 31 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; 32 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 33 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; 34 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 35 import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; 36 import static android.net.NetworkRequest.Type.REQUEST; 37 import static android.net.NetworkRequest.Type.TRACK_DEFAULT; 38 import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; 39 40 import static com.android.testutils.MiscAsserts.assertThrows; 41 42 import static org.junit.Assert.assertFalse; 43 import static org.junit.Assert.assertNotNull; 44 import static org.junit.Assert.assertNull; 45 import static org.junit.Assert.assertTrue; 46 import static org.junit.Assert.fail; 47 import static org.mockito.ArgumentMatchers.anyBoolean; 48 import static org.mockito.ArgumentMatchers.eq; 49 import static org.mockito.ArgumentMatchers.nullable; 50 import static org.mockito.Mockito.CALLS_REAL_METHODS; 51 import static org.mockito.Mockito.after; 52 import static org.mockito.Mockito.any; 53 import static org.mockito.Mockito.anyInt; 54 import static org.mockito.Mockito.mock; 55 import static org.mockito.Mockito.never; 56 import static org.mockito.Mockito.reset; 57 import static org.mockito.Mockito.timeout; 58 import static org.mockito.Mockito.times; 59 import static org.mockito.Mockito.verify; 60 import static org.mockito.Mockito.when; 61 62 import android.app.PendingIntent; 63 import android.content.Context; 64 import android.content.pm.ApplicationInfo; 65 import android.net.ConnectivityManager.NetworkCallback; 66 import android.os.Build.VERSION_CODES; 67 import android.os.Bundle; 68 import android.os.Handler; 69 import android.os.Looper; 70 import android.os.Message; 71 import android.os.Messenger; 72 import android.os.Process; 73 74 import androidx.test.filters.SmallTest; 75 76 import com.android.internal.util.test.BroadcastInterceptingContext; 77 import com.android.testutils.DevSdkIgnoreRule; 78 import com.android.testutils.DevSdkIgnoreRunner; 79 80 import org.junit.Before; 81 import org.junit.Test; 82 import org.junit.runner.RunWith; 83 import org.mockito.ArgumentCaptor; 84 import org.mockito.Mock; 85 import org.mockito.MockitoAnnotations; 86 87 import java.lang.ref.WeakReference; 88 89 @RunWith(DevSdkIgnoreRunner.class) 90 @SmallTest 91 @DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R) 92 public class ConnectivityManagerTest { 93 private static final int TIMEOUT_MS = 30_000; 94 private static final int SHORT_TIMEOUT_MS = 150; 95 96 @Mock Context mCtx; 97 @Mock IConnectivityManager mService; 98 99 @Before setUp()100 public void setUp() { 101 MockitoAnnotations.initMocks(this); 102 } 103 verifyNetworkCapabilities( int legacyType, int transportType, int... capabilities)104 static NetworkCapabilities verifyNetworkCapabilities( 105 int legacyType, int transportType, int... capabilities) { 106 final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); 107 assertNotNull(nc); 108 assertTrue(nc.hasTransport(transportType)); 109 for (int capability : capabilities) { 110 assertTrue(nc.hasCapability(capability)); 111 } 112 113 return nc; 114 } 115 verifyUnrestrictedNetworkCapabilities(int legacyType, int transportType)116 static void verifyUnrestrictedNetworkCapabilities(int legacyType, int transportType) { 117 verifyNetworkCapabilities( 118 legacyType, 119 transportType, 120 NET_CAPABILITY_INTERNET, 121 NET_CAPABILITY_NOT_RESTRICTED, 122 NET_CAPABILITY_NOT_VPN, 123 NET_CAPABILITY_TRUSTED); 124 } 125 verifyRestrictedMobileNetworkCapabilities(int legacyType, int capability)126 static void verifyRestrictedMobileNetworkCapabilities(int legacyType, int capability) { 127 final NetworkCapabilities nc = verifyNetworkCapabilities( 128 legacyType, 129 TRANSPORT_CELLULAR, 130 capability, 131 NET_CAPABILITY_NOT_VPN, 132 NET_CAPABILITY_TRUSTED); 133 134 assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); 135 assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); 136 } 137 138 @Test testNetworkCapabilitiesForTypeMobile()139 public void testNetworkCapabilitiesForTypeMobile() { 140 verifyUnrestrictedNetworkCapabilities( 141 ConnectivityManager.TYPE_MOBILE, TRANSPORT_CELLULAR); 142 } 143 144 @Test testNetworkCapabilitiesForTypeMobileCbs()145 public void testNetworkCapabilitiesForTypeMobileCbs() { 146 verifyRestrictedMobileNetworkCapabilities( 147 ConnectivityManager.TYPE_MOBILE_CBS, NET_CAPABILITY_CBS); 148 } 149 150 @Test testNetworkCapabilitiesForTypeMobileDun()151 public void testNetworkCapabilitiesForTypeMobileDun() { 152 verifyRestrictedMobileNetworkCapabilities( 153 ConnectivityManager.TYPE_MOBILE_DUN, NET_CAPABILITY_DUN); 154 } 155 156 @Test testNetworkCapabilitiesForTypeMobileFota()157 public void testNetworkCapabilitiesForTypeMobileFota() { 158 verifyRestrictedMobileNetworkCapabilities( 159 ConnectivityManager.TYPE_MOBILE_FOTA, NET_CAPABILITY_FOTA); 160 } 161 162 @Test testNetworkCapabilitiesForTypeMobileHipri()163 public void testNetworkCapabilitiesForTypeMobileHipri() { 164 verifyUnrestrictedNetworkCapabilities( 165 ConnectivityManager.TYPE_MOBILE_HIPRI, TRANSPORT_CELLULAR); 166 } 167 168 @Test testNetworkCapabilitiesForTypeMobileIms()169 public void testNetworkCapabilitiesForTypeMobileIms() { 170 verifyRestrictedMobileNetworkCapabilities( 171 ConnectivityManager.TYPE_MOBILE_IMS, NET_CAPABILITY_IMS); 172 } 173 174 @Test testNetworkCapabilitiesForTypeMobileMms()175 public void testNetworkCapabilitiesForTypeMobileMms() { 176 final NetworkCapabilities nc = verifyNetworkCapabilities( 177 ConnectivityManager.TYPE_MOBILE_MMS, 178 TRANSPORT_CELLULAR, 179 NET_CAPABILITY_MMS, 180 NET_CAPABILITY_NOT_VPN, 181 NET_CAPABILITY_TRUSTED); 182 183 assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); 184 } 185 186 @Test testNetworkCapabilitiesForTypeMobileSupl()187 public void testNetworkCapabilitiesForTypeMobileSupl() { 188 final NetworkCapabilities nc = verifyNetworkCapabilities( 189 ConnectivityManager.TYPE_MOBILE_SUPL, 190 TRANSPORT_CELLULAR, 191 NET_CAPABILITY_SUPL, 192 NET_CAPABILITY_NOT_VPN, 193 NET_CAPABILITY_TRUSTED); 194 195 assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); 196 } 197 198 @Test testNetworkCapabilitiesForTypeWifi()199 public void testNetworkCapabilitiesForTypeWifi() { 200 verifyUnrestrictedNetworkCapabilities( 201 ConnectivityManager.TYPE_WIFI, TRANSPORT_WIFI); 202 } 203 204 @Test testNetworkCapabilitiesForTypeWifiP2p()205 public void testNetworkCapabilitiesForTypeWifiP2p() { 206 final NetworkCapabilities nc = verifyNetworkCapabilities( 207 ConnectivityManager.TYPE_WIFI_P2P, 208 TRANSPORT_WIFI, 209 NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_VPN, 210 NET_CAPABILITY_TRUSTED, NET_CAPABILITY_WIFI_P2P); 211 212 assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); 213 } 214 215 @Test testNetworkCapabilitiesForTypeBluetooth()216 public void testNetworkCapabilitiesForTypeBluetooth() { 217 verifyUnrestrictedNetworkCapabilities( 218 ConnectivityManager.TYPE_BLUETOOTH, TRANSPORT_BLUETOOTH); 219 } 220 221 @Test testNetworkCapabilitiesForTypeEthernet()222 public void testNetworkCapabilitiesForTypeEthernet() { 223 verifyUnrestrictedNetworkCapabilities( 224 ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET); 225 } 226 227 @Test testCallbackRelease()228 public void testCallbackRelease() throws Exception { 229 ConnectivityManager manager = new ConnectivityManager(mCtx, mService); 230 NetworkRequest request = makeRequest(1); 231 NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class, 232 CALLS_REAL_METHODS); 233 Handler handler = new Handler(Looper.getMainLooper()); 234 ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); 235 236 // register callback 237 when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), 238 anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request); 239 manager.requestNetwork(request, callback, handler); 240 241 // callback triggers 242 captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE)); 243 verify(callback, timeout(TIMEOUT_MS).times(1)).onAvailable(any(Network.class), 244 any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean()); 245 246 // unregister callback 247 manager.unregisterNetworkCallback(callback); 248 verify(mService, times(1)).releaseNetworkRequest(request); 249 250 // callback does not trigger anymore. 251 captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING)); 252 verify(callback, after(SHORT_TIMEOUT_MS).never()).onLosing(any(), anyInt()); 253 } 254 255 @Test testCallbackRecycling()256 public void testCallbackRecycling() throws Exception { 257 ConnectivityManager manager = new ConnectivityManager(mCtx, mService); 258 NetworkRequest req1 = makeRequest(1); 259 NetworkRequest req2 = makeRequest(2); 260 NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class, 261 CALLS_REAL_METHODS); 262 Handler handler = new Handler(Looper.getMainLooper()); 263 ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); 264 265 // register callback 266 when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), 267 anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1); 268 manager.requestNetwork(req1, callback, handler); 269 270 // callback triggers 271 captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE)); 272 verify(callback, timeout(TIMEOUT_MS).times(1)).onAvailable(any(Network.class), 273 any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean()); 274 275 // unregister callback 276 manager.unregisterNetworkCallback(callback); 277 verify(mService, times(1)).releaseNetworkRequest(req1); 278 279 // callback does not trigger anymore. 280 captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING)); 281 verify(callback, after(SHORT_TIMEOUT_MS).never()).onLosing(any(), anyInt()); 282 283 // callback can be registered again 284 when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), 285 anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2); 286 manager.requestNetwork(req2, callback, handler); 287 288 // callback triggers 289 captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST)); 290 verify(callback, timeout(TIMEOUT_MS).times(1)).onLost(any()); 291 292 // unregister callback 293 manager.unregisterNetworkCallback(callback); 294 verify(mService, times(1)).releaseNetworkRequest(req2); 295 } 296 297 // TODO: turn on this test when request callback 1:1 mapping is enforced 298 //@Test noDoubleCallbackRegistration()299 private void noDoubleCallbackRegistration() throws Exception { 300 ConnectivityManager manager = new ConnectivityManager(mCtx, mService); 301 NetworkRequest request = makeRequest(1); 302 NetworkCallback callback = new ConnectivityManager.NetworkCallback(); 303 ApplicationInfo info = new ApplicationInfo(); 304 // TODO: update version when starting to enforce 1:1 mapping 305 info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; 306 307 when(mCtx.getApplicationInfo()).thenReturn(info); 308 when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), 309 anyInt(), any(), nullable(String.class))).thenReturn(request); 310 311 Handler handler = new Handler(Looper.getMainLooper()); 312 manager.requestNetwork(request, callback, handler); 313 314 // callback is already registered, reregistration should fail. 315 Class<IllegalArgumentException> wantException = IllegalArgumentException.class; 316 expectThrowable(() -> manager.requestNetwork(request, callback), wantException); 317 318 manager.unregisterNetworkCallback(callback); 319 verify(mService, times(1)).releaseNetworkRequest(request); 320 321 // unregistering the callback should make it registrable again. 322 manager.requestNetwork(request, callback); 323 } 324 325 @Test testDefaultNetworkActiveListener()326 public void testDefaultNetworkActiveListener() throws Exception { 327 final ConnectivityManager manager = new ConnectivityManager(mCtx, mService); 328 final ConnectivityManager.OnNetworkActiveListener listener = 329 mock(ConnectivityManager.OnNetworkActiveListener.class); 330 assertThrows(IllegalArgumentException.class, 331 () -> manager.removeDefaultNetworkActiveListener(listener)); 332 manager.addDefaultNetworkActiveListener(listener); 333 verify(mService, times(1)).registerNetworkActivityListener(any()); 334 manager.removeDefaultNetworkActiveListener(listener); 335 verify(mService, times(1)).unregisterNetworkActivityListener(any()); 336 assertThrows(IllegalArgumentException.class, 337 () -> manager.removeDefaultNetworkActiveListener(listener)); 338 } 339 340 @Test testArgumentValidation()341 public void testArgumentValidation() throws Exception { 342 ConnectivityManager manager = new ConnectivityManager(mCtx, mService); 343 344 NetworkRequest request = mock(NetworkRequest.class); 345 NetworkCallback callback = mock(NetworkCallback.class); 346 Handler handler = mock(Handler.class); 347 NetworkCallback nullCallback = null; 348 PendingIntent nullIntent = null; 349 350 mustFail(() -> manager.requestNetwork(null, callback)); 351 mustFail(() -> manager.requestNetwork(request, nullCallback)); 352 mustFail(() -> manager.requestNetwork(request, callback, null)); 353 mustFail(() -> manager.requestNetwork(request, callback, -1)); 354 mustFail(() -> manager.requestNetwork(request, nullIntent)); 355 356 mustFail(() -> manager.requestBackgroundNetwork(null, callback, handler)); 357 mustFail(() -> manager.requestBackgroundNetwork(request, null, handler)); 358 mustFail(() -> manager.requestBackgroundNetwork(request, callback, null)); 359 360 mustFail(() -> manager.registerNetworkCallback(null, callback, handler)); 361 mustFail(() -> manager.registerNetworkCallback(request, null, handler)); 362 mustFail(() -> manager.registerNetworkCallback(request, callback, null)); 363 mustFail(() -> manager.registerNetworkCallback(request, nullIntent)); 364 365 mustFail(() -> manager.registerDefaultNetworkCallback(null, handler)); 366 mustFail(() -> manager.registerDefaultNetworkCallback(callback, null)); 367 368 mustFail(() -> manager.registerSystemDefaultNetworkCallback(null, handler)); 369 mustFail(() -> manager.registerSystemDefaultNetworkCallback(callback, null)); 370 371 mustFail(() -> manager.registerBestMatchingNetworkCallback(null, callback, handler)); 372 mustFail(() -> manager.registerBestMatchingNetworkCallback(request, null, handler)); 373 mustFail(() -> manager.registerBestMatchingNetworkCallback(request, callback, null)); 374 375 mustFail(() -> manager.unregisterNetworkCallback(nullCallback)); 376 mustFail(() -> manager.unregisterNetworkCallback(nullIntent)); 377 mustFail(() -> manager.releaseNetworkRequest(nullIntent)); 378 } 379 mustFail(Runnable fn)380 static void mustFail(Runnable fn) { 381 try { 382 fn.run(); 383 fail(); 384 } catch (Exception expected) { 385 } 386 } 387 388 @Test testRequestType()389 public void testRequestType() throws Exception { 390 final String testPkgName = "MyPackage"; 391 final String testAttributionTag = "MyTag"; 392 final ConnectivityManager manager = new ConnectivityManager(mCtx, mService); 393 when(mCtx.getOpPackageName()).thenReturn(testPkgName); 394 when(mCtx.getAttributionTag()).thenReturn(testAttributionTag); 395 final NetworkRequest request = makeRequest(1); 396 final NetworkCallback callback = new ConnectivityManager.NetworkCallback(); 397 398 manager.requestNetwork(request, callback); 399 verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), 400 eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), 401 eq(testPkgName), eq(testAttributionTag)); 402 reset(mService); 403 404 // Verify that register network callback does not calls requestNetwork at all. 405 manager.registerNetworkCallback(request, callback); 406 verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), 407 anyInt(), anyInt(), any(), any()); 408 verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(), 409 eq(testPkgName), eq(testAttributionTag)); 410 reset(mService); 411 412 Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); 413 414 manager.registerDefaultNetworkCallback(callback); 415 verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), 416 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), 417 eq(testPkgName), eq(testAttributionTag)); 418 reset(mService); 419 420 manager.registerDefaultNetworkCallbackForUid(42, callback, handler); 421 verify(mService).requestNetwork(eq(42), eq(null), 422 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), 423 eq(testPkgName), eq(testAttributionTag)); 424 425 manager.requestBackgroundNetwork(request, callback, handler); 426 verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), 427 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), 428 eq(testPkgName), eq(testAttributionTag)); 429 reset(mService); 430 431 manager.registerSystemDefaultNetworkCallback(callback, handler); 432 verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), 433 eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), 434 eq(testPkgName), eq(testAttributionTag)); 435 reset(mService); 436 } 437 makeMessage(NetworkRequest req, int messageType)438 static Message makeMessage(NetworkRequest req, int messageType) { 439 Bundle bundle = new Bundle(); 440 bundle.putParcelable(NetworkRequest.class.getSimpleName(), req); 441 // Pass default objects as we don't care which get passed here 442 bundle.putParcelable(Network.class.getSimpleName(), new Network(1)); 443 bundle.putParcelable(NetworkCapabilities.class.getSimpleName(), new NetworkCapabilities()); 444 bundle.putParcelable(LinkProperties.class.getSimpleName(), new LinkProperties()); 445 Message msg = Message.obtain(); 446 msg.what = messageType; 447 msg.setData(bundle); 448 return msg; 449 } 450 makeRequest(int requestId)451 static NetworkRequest makeRequest(int requestId) { 452 NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); 453 return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE, 454 requestId, NetworkRequest.Type.NONE); 455 } 456 expectThrowable(Runnable block, Class<? extends Throwable> throwableType)457 static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) { 458 try { 459 block.run(); 460 } catch (Throwable t) { 461 if (t.getClass().equals(throwableType)) { 462 return; 463 } 464 fail("expected exception of type " + throwableType + ", but was " + t.getClass()); 465 } 466 fail("expected exception of type " + throwableType); 467 } 468 469 private static class MockContext extends BroadcastInterceptingContext { MockContext(Context base)470 MockContext(Context base) { 471 super(base); 472 } 473 474 @Override getApplicationContext()475 public Context getApplicationContext() { 476 return mock(Context.class); 477 } 478 } 479 makeConnectivityManagerAndReturnContext()480 private WeakReference<Context> makeConnectivityManagerAndReturnContext() { 481 // Mockito may have an internal reference to the mock, creating MockContext for testing. 482 final Context c = new MockContext(mock(Context.class)); 483 484 new ConnectivityManager(c, mService); 485 486 return new WeakReference<>(c); 487 } 488 forceGC()489 private void forceGC() { 490 // First GC ensures that objects are collected for finalization, then second GC ensures 491 // they're garbage-collected after being finalized. 492 System.gc(); 493 System.runFinalization(); 494 System.gc(); 495 } 496 497 @Test testConnectivityManagerDoesNotLeakContext()498 public void testConnectivityManagerDoesNotLeakContext() throws Exception { 499 final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext(); 500 501 final int attempts = 600; 502 final long waitIntervalMs = 50; 503 for (int i = 0; i < attempts; i++) { 504 forceGC(); 505 if (ref.get() == null) break; 506 507 Thread.sleep(waitIntervalMs); 508 } 509 510 assertNull("ConnectivityManager weak reference still not null after " + attempts 511 + " attempts", ref.get()); 512 } 513 } 514