1 // Copyright 2021 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.android.downloader; 16 17 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; 18 import static android.os.Looper.getMainLooper; 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertThrows; 21 import static org.robolectric.Shadows.shadowOf; 22 23 import android.Manifest.permission; 24 import android.app.Application; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.net.ConnectivityManager; 28 import android.net.Network; 29 import android.net.NetworkCapabilities; 30 import android.net.NetworkInfo.DetailedState; 31 import android.net.NetworkInfo.State; 32 import android.os.Build.VERSION_CODES; 33 import androidx.test.core.app.ApplicationProvider; 34 import com.google.android.downloader.DownloadConstraints.NetworkType; 35 import com.google.common.collect.ImmutableSet; 36 import com.google.common.util.concurrent.ListenableFuture; 37 import java.time.Duration; 38 import java.util.EnumSet; 39 import java.util.concurrent.ExecutionException; 40 import java.util.concurrent.ScheduledExecutorService; 41 import java.util.concurrent.TimeoutException; 42 import org.junit.Before; 43 import org.junit.Rule; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 import org.robolectric.RobolectricTestRunner; 47 import org.robolectric.annotation.Config; 48 import org.robolectric.shadows.ShadowApplication; 49 import org.robolectric.shadows.ShadowConnectivityManager; 50 import org.robolectric.shadows.ShadowNetwork; 51 import org.robolectric.shadows.ShadowNetworkCapabilities; 52 import org.robolectric.shadows.ShadowNetworkInfo; 53 54 /** Unit tests for AndroidConnectivityHandler. */ 55 @RunWith(RobolectricTestRunner.class) 56 @Config(sdk = VERSION_CODES.KITKAT) 57 public final class AndroidConnectivityHandlerTest { 58 private static final long DEFAULT_TIMEOUT_MILLIS = 1000; 59 60 @Rule 61 public TestExecutorRule testExecutorRule = 62 new TestExecutorRule(Duration.ofMillis(2 * DEFAULT_TIMEOUT_MILLIS)); 63 64 private Application application; 65 private ConnectivityManager connectivityManager; 66 private ScheduledExecutorService scheduledExecutorService; 67 68 @Before setUp()69 public void setUp() { 70 application = ApplicationProvider.getApplicationContext(); 71 scheduledExecutorService = testExecutorRule.newSingleThreadScheduledExecutor(); 72 connectivityManager = 73 (ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE); 74 } 75 76 @Test checkConnectivity_permissionNotGranted()77 public void checkConnectivity_permissionNotGranted() { 78 assertThrows(IllegalStateException.class, this::createConnectivityHandler); 79 } 80 81 @Test checkConnectivity_noConnectivityRequired()82 public void checkConnectivity_noConnectivityRequired() throws Exception { 83 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 84 85 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 86 ListenableFuture<Void> result = connectivityHandler.checkConnectivity(DownloadConstraints.NONE); 87 88 assertThat(result.isDone()).isTrue(); 89 assertThat(result.get()).isNull(); 90 } 91 92 @Test checkConnectivity_noNetwork()93 public void checkConnectivity_noNetwork() { 94 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 95 96 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 97 98 shadowOf(connectivityManager).setActiveNetworkInfo(null); 99 100 ListenableFuture<Void> result = 101 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_CONNECTED); 102 103 assertThat(result.isDone()).isFalse(); 104 } 105 106 @Test checkConnectivity_networkNotConnected()107 public void checkConnectivity_networkNotConnected() { 108 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 109 110 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 111 112 shadowOf(connectivityManager) 113 .setActiveNetworkInfo( 114 ShadowNetworkInfo.newInstance( 115 DetailedState.DISCONNECTED, 116 ConnectivityManager.TYPE_WIFI, 117 0, 118 false, 119 State.DISCONNECTED)); 120 121 ListenableFuture<Void> result = 122 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_CONNECTED); 123 124 assertThat(result.isDone()).isFalse(); 125 } 126 127 @Test checkConnectivity_wrongNetworkType()128 public void checkConnectivity_wrongNetworkType() { 129 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 130 131 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 132 133 shadowOf(connectivityManager) 134 .setActiveNetworkInfo( 135 ShadowNetworkInfo.newInstance( 136 DetailedState.CONNECTED, 137 ConnectivityManager.TYPE_MOBILE, 138 0, 139 true, 140 State.CONNECTED)); 141 142 ListenableFuture<Void> result = 143 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_UNMETERED); 144 145 assertThat(result.isDone()).isFalse(); 146 } 147 148 @Test checkConnectivity_anyNetworkType()149 public void checkConnectivity_anyNetworkType() { 150 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 151 152 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 153 154 shadowOf(connectivityManager) 155 .setActiveNetworkInfo( 156 ShadowNetworkInfo.newInstance( 157 DetailedState.CONNECTED, 158 ConnectivityManager.TYPE_MOBILE, 159 0, 160 true, 161 State.CONNECTED)); 162 163 ListenableFuture<Void> result = 164 connectivityHandler.checkConnectivity( 165 DownloadConstraints.builder() 166 .setRequireUnmeteredNetwork(false) 167 .setRequiredNetworkTypes(ImmutableSet.of(NetworkType.ANY)) 168 .build()); 169 assertThat(result.isDone()).isTrue(); 170 } 171 172 @Test checkConnectivity_unknownNetworkType()173 public void checkConnectivity_unknownNetworkType() { 174 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 175 176 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 177 178 shadowOf(connectivityManager) 179 .setActiveNetworkInfo( 180 ShadowNetworkInfo.newInstance( 181 DetailedState.CONNECTED, 182 100, // Invalid network type 183 0, 184 true, 185 State.CONNECTED)); 186 187 ListenableFuture<Void> result = 188 connectivityHandler.checkConnectivity( 189 DownloadConstraints.builder() 190 .setRequiredNetworkTypes(EnumSet.of(NetworkType.WIFI)) 191 .setRequireUnmeteredNetwork(false) 192 .build()); 193 194 assertThat(result.isDone()).isFalse(); 195 } 196 197 @Test checkConnectivity_requiredNetworkConnected_wifiOnly()198 public void checkConnectivity_requiredNetworkConnected_wifiOnly() { 199 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 200 201 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 202 203 shadowOf(connectivityManager) 204 .setActiveNetworkInfo( 205 ShadowNetworkInfo.newInstance( 206 DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, State.CONNECTED)); 207 208 ListenableFuture<Void> result = 209 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_UNMETERED); 210 211 assertThat(result.isDone()).isTrue(); 212 } 213 214 @Test checkConnectivity_requiredNetworkConnected_wifiOrCellular()215 public void checkConnectivity_requiredNetworkConnected_wifiOrCellular() { 216 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 217 218 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 219 220 shadowOf(connectivityManager) 221 .setActiveNetworkInfo( 222 ShadowNetworkInfo.newInstance( 223 DetailedState.CONNECTED, 224 ConnectivityManager.TYPE_MOBILE, 225 0, 226 true, 227 State.CONNECTED)); 228 229 ListenableFuture<Void> result = 230 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_CONNECTED); 231 232 assertThat(result.isDone()).isTrue(); 233 } 234 235 @Config(sdk = VERSION_CODES.M) 236 @Test checkConnectivity_requiredNetworkConnected_sdk23()237 public void checkConnectivity_requiredNetworkConnected_sdk23() { 238 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 239 240 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 241 242 ShadowConnectivityManager shadowConnectivityManager = shadowOf(connectivityManager); 243 Network network = ShadowNetwork.newInstance(0); 244 shadowConnectivityManager.addNetwork( 245 network, 246 ShadowNetworkInfo.newInstance( 247 DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, State.CONNECTED)); 248 NetworkCapabilities networkCapabilities = ShadowNetworkCapabilities.newInstance(); 249 shadowOf(networkCapabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 250 shadowConnectivityManager.setNetworkCapabilities(network, networkCapabilities); 251 shadowConnectivityManager.setDefaultNetworkActive(true); 252 253 ListenableFuture<Void> result = 254 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_CONNECTED); 255 256 assertThat(result.isDone()).isTrue(); 257 } 258 259 @Test checkConnectivity_networkChange()260 public void checkConnectivity_networkChange() throws Exception { 261 ShadowApplication shadowApplication = shadowOf(application); 262 shadowApplication.grantPermissions(permission.ACCESS_NETWORK_STATE); 263 264 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 265 266 shadowOf(connectivityManager) 267 .setActiveNetworkInfo( 268 ShadowNetworkInfo.newInstance( 269 DetailedState.DISCONNECTED, 270 ConnectivityManager.TYPE_WIFI, 271 0, 272 false, 273 State.DISCONNECTED)); 274 275 ListenableFuture<Void> result = 276 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_CONNECTED); 277 assertThat(result.isDone()).isFalse(); 278 assertThat(shadowApplication.getRegisteredReceivers()).hasSize(1); 279 assertThat(shadowApplication.getRegisteredReceivers().get(0).getBroadcastReceiver()) 280 .isInstanceOf(AndroidConnectivityHandler.NetworkBroadcastReceiver.class); 281 assertThat( 282 shadowApplication 283 .getRegisteredReceivers() 284 .get(0) 285 .getIntentFilter() 286 .hasAction(CONNECTIVITY_ACTION)) 287 .isTrue(); 288 289 // Change state to be available. 290 shadowOf(connectivityManager) 291 .setActiveNetworkInfo( 292 ShadowNetworkInfo.newInstance( 293 DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, State.CONNECTED)); 294 295 application.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); 296 297 shadowOf(getMainLooper()).idle(); 298 299 assertThat(result.isDone()).isTrue(); 300 assertThat(result.get()).isNull(); 301 assertThat(shadowApplication.getRegisteredReceivers()).isEmpty(); 302 } 303 304 @Test checkConnectivity_timeout()305 public void checkConnectivity_timeout() { 306 shadowOf(application).grantPermissions(permission.ACCESS_NETWORK_STATE); 307 308 AndroidConnectivityHandler connectivityHandler = createConnectivityHandler(); 309 310 shadowOf(connectivityManager) 311 .setActiveNetworkInfo( 312 ShadowNetworkInfo.newInstance( 313 DetailedState.DISCONNECTED, 314 ConnectivityManager.TYPE_WIFI, 315 0, 316 false, 317 State.DISCONNECTED)); 318 319 ListenableFuture<Void> result = 320 connectivityHandler.checkConnectivity(DownloadConstraints.NETWORK_CONNECTED); 321 assertThat(result.isDone()).isFalse(); 322 323 ExecutionException exception = assertThrows(ExecutionException.class, result::get); 324 assertThat(exception).hasCauseThat().isInstanceOf(TimeoutException.class); 325 } 326 createConnectivityHandler()327 private AndroidConnectivityHandler createConnectivityHandler() { 328 return new AndroidConnectivityHandler( 329 application, scheduledExecutorService, DEFAULT_TIMEOUT_MILLIS); 330 } 331 } 332