1 /* 2 * Copyright (C) 2014 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 package android.jobscheduler.cts; 17 18 19 import android.annotation.TargetApi; 20 import android.app.job.JobInfo; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.PackageManager; 26 import android.net.ConnectivityManager; 27 import android.net.NetworkInfo; 28 import android.net.wifi.WifiManager; 29 import android.util.Log; 30 31 import com.android.compatibility.common.util.SystemUtil; 32 33 import java.util.concurrent.CountDownLatch; 34 import java.util.concurrent.TimeUnit; 35 36 /** 37 * Schedules jobs with the {@link android.app.job.JobScheduler} that have network connectivity 38 * constraints. 39 * Requires manipulating the {@link android.net.wifi.WifiManager} to ensure an unmetered network. 40 * Similarly, requires that the phone be connected to a wifi hotspot, or else the test will fail. 41 */ 42 @TargetApi(21) 43 public class ConnectivityConstraintTest extends ConstraintTest { 44 private static final String TAG = "ConnectivityConstraintTest"; 45 private static final String RESTRICT_BACKGROUND_GET_CMD = 46 "cmd netpolicy get restrict-background"; 47 private static final String RESTRICT_BACKGROUND_ON_CMD = 48 "cmd netpolicy set restrict-background true"; 49 private static final String RESTRICT_BACKGROUND_OFF_CMD = 50 "cmd netpolicy set restrict-background false"; 51 52 /** Unique identifier for the job scheduled by this suite of tests. */ 53 public static final int CONNECTIVITY_JOB_ID = ConnectivityConstraintTest.class.hashCode(); 54 55 private WifiManager mWifiManager; 56 private ConnectivityManager mCm; 57 58 /** Whether the device running these tests supports WiFi. */ 59 private boolean mHasWifi; 60 /** Whether the device running these tests supports telephony. */ 61 private boolean mHasTelephony; 62 /** Track whether WiFi was enabled in case we turn it off. */ 63 private boolean mInitialWiFiState; 64 /** Track whether restrict background policy was enabled in case we turn it off. */ 65 private boolean mInitialRestrictBackground; 66 67 private JobInfo.Builder mBuilder; 68 69 private TestAppInterface mTestAppInterface; 70 71 @Override setUp()72 public void setUp() throws Exception { 73 super.setUp(); 74 75 mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); 76 mCm = 77 (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 78 79 PackageManager packageManager = mContext.getPackageManager(); 80 mHasWifi = packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI); 81 mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 82 mBuilder = 83 new JobInfo.Builder(CONNECTIVITY_JOB_ID, kJobServiceComponent); 84 85 mInitialWiFiState = mWifiManager.isWifiEnabled(); 86 mInitialRestrictBackground = SystemUtil 87 .runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD) 88 .contains("enabled"); 89 } 90 91 @Override tearDown()92 public void tearDown() throws Exception { 93 if (mTestAppInterface != null) { 94 mTestAppInterface.cleanup(); 95 } 96 mJobScheduler.cancel(CONNECTIVITY_JOB_ID); 97 98 // Restore initial restrict background data usage policy 99 setDataSaverEnabled(mInitialRestrictBackground); 100 101 // Ensure that we leave WiFi in its previous state. 102 if (mWifiManager.isWifiEnabled() != mInitialWiFiState) { 103 setWifiState(mInitialWiFiState, mContext, mCm, mWifiManager); 104 } 105 106 super.tearDown(); 107 } 108 109 // -------------------------------------------------------------------------------------------- 110 // Positives - schedule jobs under conditions that require them to pass. 111 // -------------------------------------------------------------------------------------------- 112 113 /** 114 * Schedule a job that requires a WiFi connection, and assert that it executes when the device 115 * is connected to WiFi. This will fail if a wifi connection is unavailable. 116 */ testUnmeteredConstraintExecutes_withWifi()117 public void testUnmeteredConstraintExecutes_withWifi() throws Exception { 118 if (!mHasWifi) { 119 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 120 return; 121 } 122 connectToWifi(); 123 124 kTestEnvironment.setExpectedExecutions(1); 125 mJobScheduler.schedule( 126 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 127 .build()); 128 129 sendExpediteStableChargingBroadcast(); 130 131 assertTrue("Job with unmetered constraint did not fire on WiFi.", 132 kTestEnvironment.awaitExecution()); 133 } 134 135 /** 136 * Schedule a job with a connectivity constraint, and ensure that it executes on WiFi. 137 */ testConnectivityConstraintExecutes_withWifi()138 public void testConnectivityConstraintExecutes_withWifi() throws Exception { 139 if (!mHasWifi) { 140 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 141 return; 142 } 143 connectToWifi(); 144 145 kTestEnvironment.setExpectedExecutions(1); 146 mJobScheduler.schedule( 147 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 148 .build()); 149 150 sendExpediteStableChargingBroadcast(); 151 152 assertTrue("Job with connectivity constraint did not fire on WiFi.", 153 kTestEnvironment.awaitExecution()); 154 } 155 156 /** 157 * Schedule a job with a generic connectivity constraint, and ensure that it executes on WiFi, 158 * even with Data Saver on. 159 */ testConnectivityConstraintExecutes_withWifi_DataSaverOn()160 public void testConnectivityConstraintExecutes_withWifi_DataSaverOn() throws Exception { 161 if (!mHasWifi) { 162 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 163 return; 164 } 165 connectToWifi(); 166 setDataSaverEnabled(true); 167 168 kTestEnvironment.setExpectedExecutions(1); 169 mJobScheduler.schedule( 170 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 171 .build()); 172 173 sendExpediteStableChargingBroadcast(); 174 175 assertTrue("Job with connectivity constraint did not fire on WiFi.", 176 kTestEnvironment.awaitExecution()); 177 } 178 179 /** 180 * Schedule a job with a generic connectivity constraint, and ensure that it executes 181 * on a cellular data connection. 182 */ testConnectivityConstraintExecutes_withMobile()183 public void testConnectivityConstraintExecutes_withMobile() throws Exception { 184 if (!checkDeviceSupportsMobileData()) { 185 return; 186 } 187 disconnectWifiToConnectToMobile(); 188 189 kTestEnvironment.setExpectedExecutions(1); 190 mJobScheduler.schedule( 191 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 192 .build()); 193 194 sendExpediteStableChargingBroadcast(); 195 196 assertTrue("Job with connectivity constraint did not fire on mobile.", 197 kTestEnvironment.awaitExecution()); 198 } 199 200 /** 201 * Schedule a job with a generic connectivity constraint, and ensure that it isn't stopped when 202 * the device transitions to WiFi. 203 */ testConnectivityConstraintExecutes_transitionNetworks()204 public void testConnectivityConstraintExecutes_transitionNetworks() throws Exception { 205 if (!mHasWifi) { 206 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 207 return; 208 } 209 if (!checkDeviceSupportsMobileData()) { 210 return; 211 } 212 setDataSaverEnabled(false); 213 disconnectWifiToConnectToMobile(); 214 215 kTestEnvironment.setExpectedExecutions(1); 216 kTestEnvironment.setExpectedStopped(); 217 mJobScheduler.schedule( 218 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 219 .build()); 220 221 sendExpediteStableChargingBroadcast(); 222 223 assertTrue("Job with connectivity constraint did not fire on mobile.", 224 kTestEnvironment.awaitExecution()); 225 226 connectToWifi(); 227 assertFalse( 228 "Job with connectivity constraint was stopped when network transitioned to WiFi.", 229 kTestEnvironment.awaitStopped()); 230 } 231 232 /** 233 * Schedule a job with a metered connectivity constraint, and ensure that it executes 234 * on a mobile data connection. 235 */ testConnectivityConstraintExecutes_metered()236 public void testConnectivityConstraintExecutes_metered() throws Exception { 237 if (!checkDeviceSupportsMobileData()) { 238 return; 239 } 240 setDataSaverEnabled(false); 241 disconnectWifiToConnectToMobile(); 242 243 kTestEnvironment.setExpectedExecutions(1); 244 mJobScheduler.schedule( 245 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED) 246 .build()); 247 248 sendExpediteStableChargingBroadcast(); 249 assertTrue("Job with metered connectivity constraint did not fire on mobile.", 250 kTestEnvironment.awaitExecution()); 251 } 252 253 /** 254 * Schedule a job with a cellular connectivity constraint, and ensure that it executes 255 * on a mobile data connection and is not stopped when Data Saver is turned on because the app 256 * is in the foreground. 257 */ testCellularConstraintExecutedAndStopped_Foreground()258 public void testCellularConstraintExecutedAndStopped_Foreground() throws Exception { 259 if (!checkDeviceSupportsMobileData()) { 260 return; 261 } 262 setDataSaverEnabled(false); 263 disconnectWifiToConnectToMobile(); 264 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 265 mTestAppInterface.startAndKeepTestActivity(); 266 267 mTestAppInterface.scheduleJob(false, true); 268 269 sendExpediteStableChargingBroadcast(); 270 assertTrue("Job with metered connectivity constraint did not fire on mobile.", 271 mTestAppInterface.awaitJobStart(30_000)); 272 273 setDataSaverEnabled(true); 274 assertFalse( 275 "Job with metered connectivity constraint for foreground app was stopped when" 276 + " Data Saver was turned on.", 277 mTestAppInterface.awaitJobStop(30_000)); 278 } 279 280 // -------------------------------------------------------------------------------------------- 281 // Positives & Negatives - schedule jobs under conditions that require that pass initially and 282 // then fail with a constraint change. 283 // -------------------------------------------------------------------------------------------- 284 285 /** 286 * Schedule a job with a cellular connectivity constraint, and ensure that it executes 287 * on a mobile data connection and is stopped when Data Saver is turned on. 288 */ testCellularConstraintExecutedAndStopped()289 public void testCellularConstraintExecutedAndStopped() throws Exception { 290 if (!checkDeviceSupportsMobileData()) { 291 return; 292 } 293 setDataSaverEnabled(false); 294 disconnectWifiToConnectToMobile(); 295 296 kTestEnvironment.setExpectedExecutions(1); 297 kTestEnvironment.setContinueAfterStart(); 298 kTestEnvironment.setExpectedStopped(); 299 mJobScheduler.schedule( 300 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR) 301 .build()); 302 303 sendExpediteStableChargingBroadcast(); 304 assertTrue("Job with metered connectivity constraint did not fire on mobile.", 305 kTestEnvironment.awaitExecution()); 306 307 setDataSaverEnabled(true); 308 assertTrue( 309 "Job with metered connectivity constraint was not stopped when Data Saver was " 310 + "turned on.", 311 kTestEnvironment.awaitStopped()); 312 } 313 314 // -------------------------------------------------------------------------------------------- 315 // Negatives - schedule jobs under conditions that require that they fail. 316 // -------------------------------------------------------------------------------------------- 317 318 /** 319 * Schedule a job that requires a WiFi connection, and assert that it fails when the device is 320 * connected to a cellular provider. 321 * This test assumes that if the device supports a mobile data connection, then this connection 322 * will be available. 323 */ testUnmeteredConstraintFails_withMobile()324 public void testUnmeteredConstraintFails_withMobile() throws Exception { 325 if (!checkDeviceSupportsMobileData()) { 326 return; 327 } 328 disconnectWifiToConnectToMobile(); 329 330 kTestEnvironment.setExpectedExecutions(0); 331 mJobScheduler.schedule( 332 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 333 .build()); 334 sendExpediteStableChargingBroadcast(); 335 336 assertTrue("Job requiring unmetered connectivity still executed on mobile.", 337 kTestEnvironment.awaitTimeout()); 338 } 339 340 /** 341 * Schedule a job that requires a metered connection, and verify that it does not run when 342 * the device is not connected to WiFi and Data Saver is on. 343 */ testMeteredConstraintFails_withMobile_DataSaverOn()344 public void testMeteredConstraintFails_withMobile_DataSaverOn() throws Exception { 345 if (!checkDeviceSupportsMobileData()) { 346 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 347 return; 348 } 349 disconnectWifiToConnectToMobile(); 350 setDataSaverEnabled(true); 351 352 kTestEnvironment.setExpectedExecutions(0); 353 mJobScheduler.schedule( 354 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR) 355 .build()); 356 sendExpediteStableChargingBroadcast(); 357 358 assertTrue("Job requiring metered connectivity still executed on WiFi.", 359 kTestEnvironment.awaitTimeout()); 360 } 361 362 /** 363 * Schedule a job that requires a metered connection, and verify that it does not run when 364 * the device is connected to a WiFi provider. 365 * This test assumes that if the device supports a mobile data connection, then this connection 366 * will be available. 367 */ testMeteredConstraintFails_withWiFi()368 public void testMeteredConstraintFails_withWiFi() throws Exception { 369 if (!mHasWifi) { 370 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 371 return; 372 } 373 if (!checkDeviceSupportsMobileData()) { 374 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 375 return; 376 } 377 connectToWifi(); 378 379 kTestEnvironment.setExpectedExecutions(0); 380 mJobScheduler.schedule( 381 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED) 382 .build()); 383 sendExpediteStableChargingBroadcast(); 384 385 assertTrue("Job requiring metered connectivity still executed on WiFi.", 386 kTestEnvironment.awaitTimeout()); 387 } 388 389 // -------------------------------------------------------------------------------------------- 390 // Utility methods 391 // -------------------------------------------------------------------------------------------- 392 393 /** 394 * Determine whether the device running these CTS tests should be subject to tests involving 395 * mobile data. 396 * @return True if this device will support a mobile data connection. 397 */ checkDeviceSupportsMobileData()398 private boolean checkDeviceSupportsMobileData() { 399 if (!mHasTelephony) { 400 Log.d(TAG, "Skipping test that requires telephony features, not supported by this" + 401 " device"); 402 return false; 403 } 404 if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) { 405 Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE"); 406 return false; 407 } 408 return true; 409 } 410 411 /** 412 * Ensure WiFi is enabled, and block until we've verified that we are in fact connected. 413 */ connectToWifi()414 private void connectToWifi() 415 throws InterruptedException { 416 setWifiState(true, mContext, mCm, mWifiManager); 417 } 418 419 /** 420 * Ensure WiFi is disabled, and block until we've verified that we are in fact disconnected. 421 */ disconnectFromWifi()422 private void disconnectFromWifi() 423 throws InterruptedException { 424 setWifiState(false, mContext, mCm, mWifiManager); 425 } 426 427 /** 428 * Set Wifi connection to specific state , and block until we've verified 429 * that we are in the state. 430 * Taken from {@link android.net.http.cts.ApacheHttpClientTest}. 431 */ setWifiState(boolean enable, Context context, ConnectivityManager cm, WifiManager wm)432 static void setWifiState(boolean enable, Context context, ConnectivityManager cm, 433 WifiManager wm) throws InterruptedException { 434 if (enable != wm.isWifiEnabled()) { 435 NetworkInfo.State expectedState = enable ? 436 NetworkInfo.State.CONNECTED : NetworkInfo.State.DISCONNECTED; 437 ConnectivityActionReceiver receiver = 438 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 439 expectedState, cm); 440 IntentFilter filter = new IntentFilter(); 441 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 442 context.registerReceiver(receiver, filter); 443 444 if (enable) { 445 SystemUtil.runShellCommand("svc wifi enable"); 446 } else { 447 SystemUtil.runShellCommand("svc wifi disable"); 448 } 449 assertTrue("Wifi must be configured to " + (enable ? "connect" : "disconnect") 450 + " to an access point for this test.", 451 receiver.waitForStateChange()); 452 453 context.unregisterReceiver(receiver); 454 } 455 } 456 457 /** 458 * Disconnect from WiFi in an attempt to connect to cellular data. Worth noting that this is 459 * best effort - there are no public APIs to force connecting to cell data. We disable WiFi 460 * and wait for a broadcast that we're connected to cell. 461 * We will not call into this function if the device doesn't support telephony. 462 * @see #mHasTelephony 463 * @see #checkDeviceSupportsMobileData() 464 */ disconnectWifiToConnectToMobile()465 private void disconnectWifiToConnectToMobile() throws InterruptedException { 466 if (mHasWifi && mWifiManager.isWifiEnabled()) { 467 disconnectFromWifi(); 468 ConnectivityActionReceiver connectMobileReceiver = 469 new ConnectivityActionReceiver(ConnectivityManager.TYPE_MOBILE, 470 NetworkInfo.State.CONNECTED, mCm); 471 IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 472 mContext.registerReceiver(connectMobileReceiver, filter); 473 474 475 assertTrue("Device must have access to a metered network for this test.", 476 connectMobileReceiver.waitForStateChange()); 477 478 mContext.unregisterReceiver(connectMobileReceiver); 479 } 480 } 481 482 /** 483 * Ensures that restrict background data usage policy is turned off. 484 * If the policy is on, it interferes with tests that relies on metered connection. 485 */ setDataSaverEnabled(boolean enabled)486 private void setDataSaverEnabled(boolean enabled) throws Exception { 487 SystemUtil.runShellCommand(getInstrumentation(), 488 enabled ? RESTRICT_BACKGROUND_ON_CMD : RESTRICT_BACKGROUND_OFF_CMD); 489 } 490 491 /** Capture the last connectivity change's network type and state. */ 492 private static class ConnectivityActionReceiver extends BroadcastReceiver { 493 494 private final CountDownLatch mReceiveLatch = new CountDownLatch(1); 495 496 private final int mNetworkType; 497 498 private final NetworkInfo.State mExpectedState; 499 500 private final ConnectivityManager mCm; 501 ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState, ConnectivityManager cm)502 ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState, 503 ConnectivityManager cm) { 504 mNetworkType = networkType; 505 mExpectedState = expectedState; 506 mCm = cm; 507 } 508 onReceive(Context context, Intent intent)509 public void onReceive(Context context, Intent intent) { 510 // Dealing with a connectivity changed event for this network type. 511 final int networkTypeChanged = 512 intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1); 513 if (networkTypeChanged == -1) { 514 Log.e(TAG, "No network type provided in intent"); 515 return; 516 } 517 518 if (networkTypeChanged != mNetworkType) { 519 // Only track changes for the connectivity event that we are interested in. 520 return; 521 } 522 // Pull out the NetworkState object that we're interested in. Necessary because 523 // the ConnectivityManager will filter on uid for background connectivity. 524 NetworkInfo[] allNetworkInfo = mCm.getAllNetworkInfo(); 525 NetworkInfo networkInfo = null; 526 for (int i=0; i<allNetworkInfo.length; i++) { 527 NetworkInfo ni = allNetworkInfo[i]; 528 if (ni.getType() == mNetworkType) { 529 networkInfo = ni; 530 break; 531 } 532 } 533 if (networkInfo == null) { 534 Log.e(TAG, "Could not find correct network type."); 535 return; 536 } 537 538 NetworkInfo.State networkState = networkInfo.getState(); 539 Log.i(TAG, "Network type: " + mNetworkType + " State: " + networkState); 540 if (networkState == mExpectedState) { 541 mReceiveLatch.countDown(); 542 } 543 } 544 waitForStateChange()545 public boolean waitForStateChange() throws InterruptedException { 546 return mReceiveLatch.await(30, TimeUnit.SECONDS) || hasExpectedState(); 547 } 548 hasExpectedState()549 private boolean hasExpectedState() { 550 return mExpectedState == mCm.getNetworkInfo(mNetworkType).getState(); 551 } 552 } 553 554 } 555