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 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 22 23 import static com.android.compatibility.common.util.TestUtils.waitUntil; 24 25 import android.Manifest; 26 import android.annotation.TargetApi; 27 import android.app.job.JobInfo; 28 import android.app.job.JobParameters; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.PackageManager; 33 import android.net.ConnectivityManager; 34 import android.net.Network; 35 import android.net.NetworkCapabilities; 36 import android.net.NetworkRequest; 37 import android.net.wifi.WifiConfiguration; 38 import android.net.wifi.WifiManager; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.platform.test.annotations.RequiresDevice; 43 import android.provider.Settings; 44 import android.util.Log; 45 46 import com.android.compatibility.common.util.AppStandbyUtils; 47 import com.android.compatibility.common.util.BatteryUtils; 48 import com.android.compatibility.common.util.CallbackAsserter; 49 import com.android.compatibility.common.util.ShellIdentityUtils; 50 import com.android.compatibility.common.util.SystemUtil; 51 52 import junit.framework.AssertionFailedError; 53 54 import java.util.List; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 import java.util.concurrent.atomic.AtomicReference; 58 import java.util.regex.Matcher; 59 import java.util.regex.Pattern; 60 61 /** 62 * Schedules jobs with the {@link android.app.job.JobScheduler} that have network connectivity 63 * constraints. 64 * Requires manipulating the {@link android.net.wifi.WifiManager} to ensure an unmetered network. 65 * Similarly, requires that the phone be connected to a wifi hotspot, or else the test will fail. 66 */ 67 @TargetApi(21) 68 @RequiresDevice // Emulators don't always have access to wifi/network 69 public class ConnectivityConstraintTest extends BaseJobSchedulerTest { 70 private static final String TAG = "ConnectivityConstraintTest"; 71 private static final String RESTRICT_BACKGROUND_GET_CMD = 72 "cmd netpolicy get restrict-background"; 73 private static final String RESTRICT_BACKGROUND_ON_CMD = 74 "cmd netpolicy set restrict-background true"; 75 private static final String RESTRICT_BACKGROUND_OFF_CMD = 76 "cmd netpolicy set restrict-background false"; 77 78 /** Unique identifier for the job scheduled by this suite of tests. */ 79 public static final int CONNECTIVITY_JOB_ID = ConnectivityConstraintTest.class.hashCode(); 80 /** Wait this long before timing out the test. */ 81 private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds. 82 83 private WifiManager mWifiManager; 84 private ConnectivityManager mCm; 85 86 /** Whether the device running these tests supports WiFi. */ 87 private boolean mHasWifi; 88 /** Whether the device running these tests supports telephony. */ 89 private boolean mHasTelephony; 90 /** Track whether WiFi was enabled in case we turn it off. */ 91 private boolean mInitialWiFiState; 92 /** Track initial WiFi metered state. */ 93 private String mInitialWiFiMeteredState; 94 private String mInitialWiFiSSID; 95 /** Track whether restrict background policy was enabled in case we turn it off. */ 96 private boolean mInitialRestrictBackground; 97 /** Track whether airplane mode was enabled in case we toggle it. */ 98 private boolean mInitialAirplaneMode; 99 /** Track whether the restricted bucket was enabled in case we toggle it. */ 100 private String mInitialRestrictedBucketEnabled; 101 102 private JobInfo.Builder mBuilder; 103 104 private TestAppInterface mTestAppInterface; 105 106 @Override setUp()107 public void setUp() throws Exception { 108 super.setUp(); 109 110 mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); 111 mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 112 113 PackageManager packageManager = mContext.getPackageManager(); 114 mHasWifi = packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI); 115 mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 116 mBuilder = new JobInfo.Builder(CONNECTIVITY_JOB_ID, kJobServiceComponent); 117 118 if (mHasWifi) { 119 mInitialWiFiState = mWifiManager.isWifiEnabled(); 120 ensureSavedWifiNetwork(mWifiManager); 121 setWifiState(true, mCm, mWifiManager); 122 mInitialWiFiSSID = getWifiSSID(); 123 mInitialWiFiMeteredState = getWifiMeteredStatus(mInitialWiFiSSID); 124 } 125 mInitialRestrictBackground = SystemUtil 126 .runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD) 127 .contains("enabled"); 128 mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(), 129 Settings.Global.ENABLE_RESTRICTED_BUCKET); 130 setDataSaverEnabled(false); 131 mInitialAirplaneMode = isAirplaneModeOn(); 132 setAirplaneMode(false); 133 // Force the test app out of the never bucket. 134 SystemUtil.runShellCommand("am set-standby-bucket " 135 + TestAppInterface.TEST_APP_PACKAGE + " rare"); 136 } 137 138 @Override tearDown()139 public void tearDown() throws Exception { 140 if (mTestAppInterface != null) { 141 mTestAppInterface.cleanup(); 142 } 143 mJobScheduler.cancel(CONNECTIVITY_JOB_ID); 144 145 BatteryUtils.runDumpsysBatteryReset(); 146 147 // Restore initial restrict background data usage policy 148 setDataSaverEnabled(mInitialRestrictBackground); 149 150 // Restore initial restricted bucket setting. 151 Settings.Global.putString(mContext.getContentResolver(), 152 Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled); 153 154 // Ensure that we leave WiFi in its previous state. 155 if (mHasWifi) { 156 setWifiMeteredState(mInitialWiFiSSID, mInitialWiFiMeteredState); 157 if (mWifiManager.isWifiEnabled() != mInitialWiFiState) { 158 try { 159 setWifiState(mInitialWiFiState, mCm, mWifiManager); 160 } catch (AssertionFailedError e) { 161 // Don't fail the test just because wifi state wasn't set in tearDown. 162 Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e); 163 } 164 } 165 } 166 167 // Restore initial airplane mode status. Do it after setting wifi in case wifi was 168 // originally metered. 169 setAirplaneMode(mInitialAirplaneMode); 170 171 super.tearDown(); 172 } 173 174 // -------------------------------------------------------------------------------------------- 175 // Positives - schedule jobs under conditions that require them to pass. 176 // -------------------------------------------------------------------------------------------- 177 178 /** 179 * Schedule a job that requires a WiFi connection, and assert that it executes when the device 180 * is connected to WiFi. This will fail if a wifi connection is unavailable. 181 */ testUnmeteredConstraintExecutes_withWifi()182 public void testUnmeteredConstraintExecutes_withWifi() throws Exception { 183 if (!mHasWifi) { 184 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 185 return; 186 } 187 setWifiMeteredState(false); 188 189 kTestEnvironment.setExpectedExecutions(1); 190 mJobScheduler.schedule( 191 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 192 .build()); 193 194 runSatisfiedJob(CONNECTIVITY_JOB_ID); 195 196 assertTrue("Job with unmetered constraint did not fire on WiFi.", 197 kTestEnvironment.awaitExecution()); 198 } 199 200 /** 201 * Schedule a job with a connectivity constraint, and ensure that it executes on WiFi. 202 */ testConnectivityConstraintExecutes_withWifi()203 public void testConnectivityConstraintExecutes_withWifi() throws Exception { 204 if (!mHasWifi) { 205 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 206 return; 207 } 208 setWifiMeteredState(false); 209 210 kTestEnvironment.setExpectedExecutions(1); 211 mJobScheduler.schedule( 212 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 213 .build()); 214 215 runSatisfiedJob(CONNECTIVITY_JOB_ID); 216 217 assertTrue("Job with connectivity constraint did not fire on WiFi.", 218 kTestEnvironment.awaitExecution()); 219 } 220 221 /** 222 * Schedule a job with a generic connectivity constraint, and ensure that it executes on WiFi, 223 * even with Data Saver on. 224 */ testConnectivityConstraintExecutes_withWifi_DataSaverOn()225 public void testConnectivityConstraintExecutes_withWifi_DataSaverOn() throws Exception { 226 if (!mHasWifi) { 227 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 228 return; 229 } 230 setWifiMeteredState(false); 231 setDataSaverEnabled(true); 232 233 kTestEnvironment.setExpectedExecutions(1); 234 mJobScheduler.schedule( 235 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 236 .build()); 237 238 runSatisfiedJob(CONNECTIVITY_JOB_ID); 239 240 assertTrue("Job with connectivity constraint did not fire on unmetered WiFi.", 241 kTestEnvironment.awaitExecution()); 242 } 243 244 /** 245 * Schedule a job with a generic connectivity constraint, and ensure that it executes 246 * on a cellular data connection. 247 */ testConnectivityConstraintExecutes_withMobile()248 public void testConnectivityConstraintExecutes_withMobile() throws Exception { 249 if (!checkDeviceSupportsMobileData()) { 250 return; 251 } 252 disconnectWifiToConnectToMobile(); 253 254 kTestEnvironment.setExpectedExecutions(1); 255 mJobScheduler.schedule( 256 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 257 .build()); 258 259 runSatisfiedJob(CONNECTIVITY_JOB_ID); 260 261 assertTrue("Job with connectivity constraint did not fire on mobile.", 262 kTestEnvironment.awaitExecution()); 263 } 264 265 /** 266 * Schedule a job with a generic connectivity constraint, and ensure that it executes 267 * on a metered wifi connection. 268 */ testConnectivityConstraintExecutes_withMeteredWifi()269 public void testConnectivityConstraintExecutes_withMeteredWifi() throws Exception { 270 if (!mHasWifi) { 271 return; 272 } 273 setWifiMeteredState(true); 274 275 kTestEnvironment.setExpectedExecutions(1); 276 mJobScheduler.schedule( 277 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build()); 278 279 runSatisfiedJob(CONNECTIVITY_JOB_ID); 280 281 assertTrue("Job with connectivity constraint did not fire on metered wifi.", 282 kTestEnvironment.awaitExecution()); 283 } 284 285 /** 286 * Schedule a job with a generic connectivity constraint, and ensure that it isn't stopped when 287 * the device transitions to WiFi. 288 */ testConnectivityConstraintExecutes_transitionNetworks()289 public void testConnectivityConstraintExecutes_transitionNetworks() throws Exception { 290 if (!mHasWifi) { 291 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 292 return; 293 } 294 if (!checkDeviceSupportsMobileData()) { 295 return; 296 } 297 disconnectWifiToConnectToMobile(); 298 299 kTestEnvironment.setExpectedExecutions(1); 300 kTestEnvironment.setExpectedStopped(); 301 mJobScheduler.schedule( 302 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 303 .build()); 304 305 runSatisfiedJob(CONNECTIVITY_JOB_ID); 306 307 assertTrue("Job with connectivity constraint did not fire on mobile.", 308 kTestEnvironment.awaitExecution()); 309 310 connectToWifi(); 311 assertFalse( 312 "Job with connectivity constraint was stopped when network transitioned to WiFi.", 313 kTestEnvironment.awaitStopped()); 314 } 315 316 /** 317 * Schedule a job with a metered connectivity constraint, and ensure that it executes 318 * on a mobile data connection. 319 */ testConnectivityConstraintExecutes_metered_mobile()320 public void testConnectivityConstraintExecutes_metered_mobile() throws Exception { 321 if (!checkDeviceSupportsMobileData()) { 322 return; 323 } 324 disconnectWifiToConnectToMobile(); 325 326 kTestEnvironment.setExpectedExecutions(1); 327 mJobScheduler.schedule( 328 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED) 329 .build()); 330 331 runSatisfiedJob(CONNECTIVITY_JOB_ID); 332 assertTrue("Job with metered connectivity constraint did not fire on mobile.", 333 kTestEnvironment.awaitExecution()); 334 } 335 336 /** 337 * Schedule a job with a metered connectivity constraint, and ensure that it executes 338 * on a mobile data connection. 339 */ testConnectivityConstraintExecutes_metered_Wifi()340 public void testConnectivityConstraintExecutes_metered_Wifi() throws Exception { 341 if (!mHasWifi) { 342 return; 343 } 344 setWifiMeteredState(true); 345 346 347 kTestEnvironment.setExpectedExecutions(1); 348 mJobScheduler.schedule( 349 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED).build()); 350 351 // Since we equate "metered" to "cellular", the job shouldn't start. 352 runSatisfiedJob(CONNECTIVITY_JOB_ID); 353 assertTrue("Job with metered connectivity constraint fired on a metered wifi network.", 354 kTestEnvironment.awaitTimeout()); 355 } 356 357 /** 358 * Schedule a job with a cellular connectivity constraint, and ensure that it executes 359 * on a mobile data connection and is not stopped when Data Saver is turned on because the app 360 * is in the foreground. 361 */ testCellularConstraintExecutedAndStopped_Foreground()362 public void testCellularConstraintExecutedAndStopped_Foreground() throws Exception { 363 if (mHasWifi) { 364 setWifiMeteredState(true); 365 } else if (checkDeviceSupportsMobileData()) { 366 disconnectWifiToConnectToMobile(); 367 } else { 368 // No mobile or wifi. 369 return; 370 } 371 372 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 373 mTestAppInterface.startAndKeepTestActivity(); 374 toggleScreenOn(true); 375 376 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_ANY, false); 377 378 mTestAppInterface.runSatisfiedJob(); 379 assertTrue("Job with metered connectivity constraint did not fire on a metered network.", 380 mTestAppInterface.awaitJobStart(30_000)); 381 382 setDataSaverEnabled(true); 383 assertFalse( 384 "Job with metered connectivity constraint for foreground app was stopped when" 385 + " Data Saver was turned on.", 386 mTestAppInterface.awaitJobStop(30_000)); 387 } 388 389 /** 390 * Schedule an expedited job that requires a network connection, and verify that it runs even 391 * when if an app is idle. 392 */ testExpeditedJobExecutes_IdleApp()393 public void testExpeditedJobExecutes_IdleApp() throws Exception { 394 if (!AppStandbyUtils.isAppStandbyEnabled()) { 395 Log.d(TAG, "App standby not enabled"); 396 return; 397 } 398 if (mHasWifi) { 399 setWifiMeteredState(true); 400 } else if (checkDeviceSupportsMobileData()) { 401 disconnectWifiToConnectToMobile(); 402 } else { 403 Log.d(TAG, "Skipping test that requires a metered network."); 404 return; 405 } 406 407 Settings.Global.putString(mContext.getContentResolver(), 408 Settings.Global.ENABLE_RESTRICTED_BUCKET, "1"); 409 mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0"); 410 SystemUtil.runShellCommand("am set-standby-bucket " 411 + kJobServiceComponent.getPackageName() + " restricted"); 412 BatteryUtils.runDumpsysBatteryUnplug(); 413 414 kTestEnvironment.setExpectedExecutions(1); 415 mJobScheduler.schedule( 416 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 417 .setExpedited(true) 418 .build()); 419 runSatisfiedJob(CONNECTIVITY_JOB_ID); 420 421 assertTrue("Expedited job requiring connectivity did not fire when app was idle.", 422 kTestEnvironment.awaitExecution()); 423 } 424 425 /** 426 * Schedule an expedited job that requires a network connection, and verify that it runs even 427 * when Battery Saver is on. 428 */ testExpeditedJobExecutes_BatterySaverOn()429 public void testExpeditedJobExecutes_BatterySaverOn() throws Exception { 430 if (!BatteryUtils.isBatterySaverSupported()) { 431 Log.d(TAG, "Skipping test that requires battery saver support"); 432 return; 433 } 434 if (mHasWifi) { 435 setWifiMeteredState(true); 436 } else if (checkDeviceSupportsMobileData()) { 437 disconnectWifiToConnectToMobile(); 438 } else { 439 Log.d(TAG, "Skipping test that requires a metered."); 440 return; 441 } 442 443 BatteryUtils.runDumpsysBatteryUnplug(); 444 BatteryUtils.enableBatterySaver(true); 445 446 kTestEnvironment.setExpectedExecutions(1); 447 mJobScheduler.schedule( 448 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 449 .setExpedited(true) 450 .build()); 451 runSatisfiedJob(CONNECTIVITY_JOB_ID); 452 453 assertTrue( 454 "Expedited job requiring connectivity did not fire with Battery Saver on.", 455 kTestEnvironment.awaitExecution()); 456 } 457 458 /** 459 * Schedule an expedited job that requires a network connection, and verify that it runs even 460 * when Data Saver is on and the device is not connected to WiFi. 461 */ testFgExpeditedJobBypassesDataSaver()462 public void testFgExpeditedJobBypassesDataSaver() throws Exception { 463 if (mHasWifi) { 464 setWifiMeteredState(true); 465 } else if (checkDeviceSupportsMobileData()) { 466 disconnectWifiToConnectToMobile(); 467 } else { 468 Log.d(TAG, "Skipping test that requires a metered network."); 469 return; 470 } 471 setDataSaverEnabled(true); 472 473 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 474 mTestAppInterface.startAndKeepTestActivity(); 475 476 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_ANY, true); 477 mTestAppInterface.runSatisfiedJob(); 478 479 assertTrue( 480 "FG expedited job requiring metered connectivity did not fire with Data Saver on.", 481 mTestAppInterface.awaitJobStart(DEFAULT_TIMEOUT_MILLIS)); 482 } 483 484 /** 485 * Schedule an expedited job that requires a network connection, and verify that it runs even 486 * when multiple firewalls are active. 487 */ testExpeditedJobBypassesSimultaneousFirewalls_noDataSaver()488 public void testExpeditedJobBypassesSimultaneousFirewalls_noDataSaver() throws Exception { 489 if (!BatteryUtils.isBatterySaverSupported()) { 490 Log.d(TAG, "Skipping test that requires battery saver support"); 491 return; 492 } 493 if (mHasWifi) { 494 setWifiMeteredState(true); 495 } else if (checkDeviceSupportsMobileData()) { 496 disconnectWifiToConnectToMobile(); 497 } else { 498 Log.d(TAG, "Skipping test that requires a metered network."); 499 return; 500 } 501 if (!AppStandbyUtils.isAppStandbyEnabled()) { 502 Log.d(TAG, "App standby not enabled"); 503 return; 504 } 505 506 Settings.Global.putString(mContext.getContentResolver(), 507 Settings.Global.ENABLE_RESTRICTED_BUCKET, "1"); 508 mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0"); 509 SystemUtil.runShellCommand("am set-standby-bucket " 510 + kJobServiceComponent.getPackageName() + " restricted"); 511 BatteryUtils.runDumpsysBatteryUnplug(); 512 BatteryUtils.enableBatterySaver(true); 513 setDataSaverEnabled(false); 514 515 kTestEnvironment.setExpectedExecutions(1); 516 mJobScheduler.schedule( 517 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 518 .setExpedited(true) 519 .build()); 520 runSatisfiedJob(CONNECTIVITY_JOB_ID); 521 522 assertTrue("Expedited job requiring connectivity did not fire with multiple firewalls.", 523 kTestEnvironment.awaitExecution()); 524 } 525 526 // -------------------------------------------------------------------------------------------- 527 // Positives & Negatives - schedule jobs under conditions that require that pass initially and 528 // then fail with a constraint change. 529 // -------------------------------------------------------------------------------------------- 530 531 /** 532 * Schedule a job with a cellular connectivity constraint, and ensure that it executes 533 * on a mobile data connection and is stopped when Data Saver is turned on. 534 */ testCellularConstraintExecutedAndStopped()535 public void testCellularConstraintExecutedAndStopped() throws Exception { 536 if (!checkDeviceSupportsMobileData()) { 537 return; 538 } 539 disconnectWifiToConnectToMobile(); 540 541 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 542 543 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_CELLULAR, false); 544 545 mTestAppInterface.runSatisfiedJob(); 546 assertTrue("Job with cellular constraint did not fire on mobile.", 547 mTestAppInterface.awaitJobStart(DEFAULT_TIMEOUT_MILLIS)); 548 549 setDataSaverEnabled(true); 550 assertTrue( 551 "Job with cellular constraint was not stopped when Data Saver was turned on.", 552 mTestAppInterface.awaitJobStop(DEFAULT_TIMEOUT_MILLIS)); 553 } 554 testJobParametersNetwork()555 public void testJobParametersNetwork() throws Exception { 556 setAirplaneMode(false); 557 558 // Everything good. 559 final NetworkRequest nr = new NetworkRequest.Builder() 560 .addCapability(NET_CAPABILITY_INTERNET) 561 .addCapability(NET_CAPABILITY_VALIDATED) 562 .build(); 563 JobInfo ji = mBuilder.setRequiredNetwork(nr).build(); 564 565 kTestEnvironment.setExpectedExecutions(1); 566 mJobScheduler.schedule(ji); 567 runSatisfiedJob(CONNECTIVITY_JOB_ID); 568 assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution()); 569 570 JobParameters params = kTestEnvironment.getLastStartJobParameters(); 571 assertNotNull(params.getNetwork()); 572 final NetworkCapabilities capabilities = 573 getContext().getSystemService(ConnectivityManager.class) 574 .getNetworkCapabilities(params.getNetwork()); 575 assertTrue(nr.canBeSatisfiedBy(capabilities)); 576 577 // Deadline passed with no network satisfied. 578 setAirplaneMode(true); 579 ji = mBuilder 580 .setRequiredNetwork(nr) 581 .setOverrideDeadline(0) 582 .build(); 583 584 kTestEnvironment.setExpectedExecutions(1); 585 mJobScheduler.schedule(ji); 586 runSatisfiedJob(CONNECTIVITY_JOB_ID); 587 assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution()); 588 589 params = kTestEnvironment.getLastStartJobParameters(); 590 assertNull(params.getNetwork()); 591 592 // No network requested 593 setAirplaneMode(false); 594 ji = mBuilder.setRequiredNetwork(null).build(); 595 kTestEnvironment.setExpectedExecutions(1); 596 mJobScheduler.schedule(ji); 597 runSatisfiedJob(CONNECTIVITY_JOB_ID); 598 assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution()); 599 600 params = kTestEnvironment.getLastStartJobParameters(); 601 assertNull(params.getNetwork()); 602 } 603 604 // -------------------------------------------------------------------------------------------- 605 // Negatives - schedule jobs under conditions that require that they fail. 606 // -------------------------------------------------------------------------------------------- 607 608 /** 609 * Schedule a job that requires a WiFi connection, and assert that it fails when the device is 610 * connected to a cellular provider. 611 * This test assumes that if the device supports a mobile data connection, then this connection 612 * will be available. 613 */ testUnmeteredConstraintFails_withMobile()614 public void testUnmeteredConstraintFails_withMobile() throws Exception { 615 if (!checkDeviceSupportsMobileData()) { 616 return; 617 } 618 disconnectWifiToConnectToMobile(); 619 620 kTestEnvironment.setExpectedExecutions(0); 621 mJobScheduler.schedule( 622 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 623 .build()); 624 runSatisfiedJob(CONNECTIVITY_JOB_ID); 625 626 assertTrue("Job requiring unmetered connectivity still executed on mobile.", 627 kTestEnvironment.awaitTimeout()); 628 } 629 630 /** 631 * Schedule a job that requires a metered connection, and verify that it does not run when 632 * the device is not connected to WiFi and Data Saver is on. 633 */ testMeteredConstraintFails_withMobile_DataSaverOn()634 public void testMeteredConstraintFails_withMobile_DataSaverOn() throws Exception { 635 if (!checkDeviceSupportsMobileData()) { 636 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 637 return; 638 } 639 disconnectWifiToConnectToMobile(); 640 setDataSaverEnabled(true); 641 642 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 643 644 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_CELLULAR, false); 645 mTestAppInterface.runSatisfiedJob(); 646 647 assertFalse("Job requiring cellular connectivity executed with Data Saver on", 648 mTestAppInterface.awaitJobStop(DEFAULT_TIMEOUT_MILLIS)); 649 } 650 651 /** 652 * Schedule a job that requires a metered connection, and verify that it does not run when 653 * the device is not connected to WiFi and Data Saver is on. 654 */ testEJMeteredConstraintFails_withMobile_DataSaverOn()655 public void testEJMeteredConstraintFails_withMobile_DataSaverOn() throws Exception { 656 if (!checkDeviceSupportsMobileData()) { 657 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 658 return; 659 } 660 disconnectWifiToConnectToMobile(); 661 setDataSaverEnabled(true); 662 663 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 664 665 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_CELLULAR, true); 666 mTestAppInterface.runSatisfiedJob(); 667 668 assertFalse("BG expedited job requiring cellular connectivity executed with Data Saver on", 669 mTestAppInterface.awaitJobStop(DEFAULT_TIMEOUT_MILLIS)); 670 } 671 672 /** 673 * Schedule a job that requires a metered connection, and verify that it does not run when 674 * the device is connected to an unmetered WiFi provider. 675 * This test assumes that if the device supports a mobile data connection, then this connection 676 * will be available. 677 */ testMeteredConstraintFails_withWiFi()678 public void testMeteredConstraintFails_withWiFi() throws Exception { 679 if (!mHasWifi) { 680 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 681 return; 682 } 683 if (!checkDeviceSupportsMobileData()) { 684 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 685 return; 686 } 687 setWifiMeteredState(false); 688 689 kTestEnvironment.setExpectedExecutions(0); 690 mJobScheduler.schedule( 691 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED) 692 .build()); 693 runSatisfiedJob(CONNECTIVITY_JOB_ID); 694 695 assertTrue("Job requiring metered connectivity still executed on WiFi.", 696 kTestEnvironment.awaitTimeout()); 697 } 698 699 /** 700 * Schedule a job that requires an unmetered connection, and verify that it does not run when 701 * the device is connected to a metered WiFi provider. 702 */ testUnmeteredConstraintFails_withMeteredWiFi()703 public void testUnmeteredConstraintFails_withMeteredWiFi() throws Exception { 704 if (!mHasWifi) { 705 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 706 return; 707 } 708 setWifiMeteredState(true); 709 710 kTestEnvironment.setExpectedExecutions(0); 711 mJobScheduler.schedule( 712 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 713 .build()); 714 runSatisfiedJob(CONNECTIVITY_JOB_ID); 715 716 assertTrue("Job requiring unmetered connectivity still executed on metered WiFi.", 717 kTestEnvironment.awaitTimeout()); 718 } 719 720 /** 721 * Schedule a job that requires a cellular connection, and verify that it does not run when 722 * the device is connected to a WiFi provider. 723 */ testCellularConstraintFails_withWiFi()724 public void testCellularConstraintFails_withWiFi() throws Exception { 725 if (!mHasWifi) { 726 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 727 return; 728 } 729 if (!checkDeviceSupportsMobileData()) { 730 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 731 return; 732 } 733 setWifiMeteredState(false); 734 735 kTestEnvironment.setExpectedExecutions(0); 736 mJobScheduler.schedule( 737 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR).build()); 738 runSatisfiedJob(CONNECTIVITY_JOB_ID); 739 740 assertTrue("Job requiring cellular connectivity still executed on WiFi.", 741 kTestEnvironment.awaitTimeout()); 742 } 743 744 /** 745 * Schedule an expedited job that requires a network connection, and verify that it runs even 746 * when Data Saver is on and the device is not connected to WiFi. 747 */ testBgExpeditedJobDoesNotBypassDataSaver()748 public void testBgExpeditedJobDoesNotBypassDataSaver() throws Exception { 749 if (mHasWifi) { 750 setWifiMeteredState(true); 751 } else if (checkDeviceSupportsMobileData()) { 752 disconnectWifiToConnectToMobile(); 753 } else { 754 Log.d(TAG, "Skipping test that requires a metered network."); 755 return; 756 } 757 setDataSaverEnabled(true); 758 759 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 760 761 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_ANY, true); 762 mTestAppInterface.runSatisfiedJob(); 763 764 assertFalse("BG expedited job requiring connectivity fired with Data Saver on.", 765 mTestAppInterface.awaitJobStart(DEFAULT_TIMEOUT_MILLIS)); 766 } 767 768 /** 769 * Schedule an expedited job that requires a network connection, and verify that it runs even 770 * when multiple firewalls are active. 771 */ testExpeditedJobDoesNotBypassSimultaneousFirewalls_withDataSaver()772 public void testExpeditedJobDoesNotBypassSimultaneousFirewalls_withDataSaver() 773 throws Exception { 774 if (!BatteryUtils.isBatterySaverSupported()) { 775 Log.d(TAG, "Skipping test that requires battery saver support"); 776 return; 777 } 778 if (mHasWifi) { 779 setWifiMeteredState(true); 780 } else if (checkDeviceSupportsMobileData()) { 781 disconnectWifiToConnectToMobile(); 782 } else { 783 Log.d(TAG, "Skipping test that requires a metered network."); 784 return; 785 } 786 if (!AppStandbyUtils.isAppStandbyEnabled()) { 787 Log.d(TAG, "App standby not enabled"); 788 return; 789 } 790 791 Settings.Global.putString(mContext.getContentResolver(), 792 Settings.Global.ENABLE_RESTRICTED_BUCKET, "1"); 793 mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0"); 794 SystemUtil.runShellCommand("am set-standby-bucket " 795 + kJobServiceComponent.getPackageName() + " restricted"); 796 BatteryUtils.runDumpsysBatteryUnplug(); 797 BatteryUtils.enableBatterySaver(true); 798 setDataSaverEnabled(true); 799 800 mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID); 801 802 mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_ANY, true); 803 mTestAppInterface.runSatisfiedJob(); 804 805 assertFalse("Expedited job fired with multiple firewalls, including data saver.", 806 mTestAppInterface.awaitJobStart(DEFAULT_TIMEOUT_MILLIS)); 807 } 808 809 // -------------------------------------------------------------------------------------------- 810 // Utility methods 811 // -------------------------------------------------------------------------------------------- 812 813 /** 814 * Determine whether the device running these CTS tests should be subject to tests involving 815 * mobile data. 816 * @return True if this device will support a mobile data connection. 817 */ checkDeviceSupportsMobileData()818 private boolean checkDeviceSupportsMobileData() { 819 if (!mHasTelephony) { 820 Log.d(TAG, "Skipping test that requires telephony features, not supported by this" + 821 " device"); 822 return false; 823 } 824 Network[] networks = mCm.getAllNetworks(); 825 for (Network network : networks) { 826 if (mCm.getNetworkCapabilities(network) 827 .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 828 return true; 829 } 830 } 831 Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE"); 832 return false; 833 } 834 unquoteSSID(String ssid)835 private String unquoteSSID(String ssid) { 836 // SSID is returned surrounded by quotes if it can be decoded as UTF-8. 837 // Otherwise it's guaranteed not to start with a quote. 838 if (ssid.charAt(0) == '"') { 839 return ssid.substring(1, ssid.length() - 1); 840 } else { 841 return ssid; 842 } 843 } 844 getWifiSSID()845 private String getWifiSSID() { 846 final AtomicReference<String> ssid = new AtomicReference<>(); 847 SystemUtil.runWithShellPermissionIdentity(() -> { 848 ssid.set(mWifiManager.getConnectionInfo().getSSID()); 849 }, Manifest.permission.ACCESS_FINE_LOCATION); 850 return unquoteSSID(ssid.get()); 851 } 852 853 // Returns "true", "false" or "none" getWifiMeteredStatus(String ssid)854 private String getWifiMeteredStatus(String ssid) { 855 // Interestingly giving the SSID as an argument to list wifi-networks 856 // only works iff the network in question has the "false" policy. 857 // Also unfortunately runShellCommand does not pass the command to the interpreter 858 // so it's not possible to | grep the ssid. 859 final String command = "cmd netpolicy list wifi-networks"; 860 final String policyString = SystemUtil.runShellCommand(command); 861 862 final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$", 863 Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString); 864 if (!m.find()) { 865 fail("Unexpected format from cmd netpolicy (when looking for " + ssid + "): " 866 + policyString); 867 } 868 return m.group(1); 869 } 870 setWifiMeteredState(boolean metered)871 private void setWifiMeteredState(boolean metered) throws Exception { 872 if (metered) { 873 // Make sure unmetered cellular networks don't interfere. 874 setAirplaneMode(true); 875 setWifiState(true, mCm, mWifiManager); 876 } 877 final String ssid = getWifiSSID(); 878 setWifiMeteredState(ssid, metered ? "true" : "false"); 879 } 880 881 // metered should be "true", "false" or "none" setWifiMeteredState(String ssid, String metered)882 private void setWifiMeteredState(String ssid, String metered) { 883 if (metered.equals(getWifiMeteredStatus(ssid))) { 884 return; 885 } 886 SystemUtil.runShellCommand("cmd netpolicy set metered-network " + ssid + " " + metered); 887 assertEquals(getWifiMeteredStatus(ssid), metered); 888 } 889 890 /** 891 * Ensure WiFi is enabled, and block until we've verified that we are in fact connected. 892 */ connectToWifi()893 private void connectToWifi() 894 throws InterruptedException { 895 setWifiState(true, mCm, mWifiManager); 896 } 897 898 /** 899 * Ensure WiFi is disabled, and block until we've verified that we are in fact disconnected. 900 */ disconnectFromWifi()901 private void disconnectFromWifi() 902 throws InterruptedException { 903 setWifiState(false, mCm, mWifiManager); 904 } 905 906 /** Ensures that the device has a wifi network saved. */ ensureSavedWifiNetwork(WifiManager wifiManager)907 static void ensureSavedWifiNetwork(WifiManager wifiManager) { 908 final List<WifiConfiguration> savedNetworks = 909 ShellIdentityUtils.invokeMethodWithShellPermissions( 910 wifiManager, WifiManager::getConfiguredNetworks); 911 assertFalse("Need at least one saved wifi network", savedNetworks.isEmpty()); 912 } 913 914 /** 915 * Set Wifi connection to specific state, and block until we've verified 916 * that we are in the state. 917 * Taken from {@link android.net.http.cts.ApacheHttpClientTest}. 918 */ setWifiState(final boolean enable, final ConnectivityManager cm, final WifiManager wm)919 static void setWifiState(final boolean enable, 920 final ConnectivityManager cm, final WifiManager wm) throws InterruptedException { 921 if (enable != isWiFiConnected(cm, wm)) { 922 NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build(); 923 NetworkCapabilities nc = new NetworkCapabilities.Builder() 924 .addTransportType(TRANSPORT_WIFI) 925 .build(); 926 NetworkTracker tracker = new NetworkTracker(nc, enable, cm); 927 cm.registerNetworkCallback(nr, tracker); 928 929 if (enable) { 930 SystemUtil.runShellCommand("svc wifi enable"); 931 //noinspection deprecation 932 SystemUtil.runWithShellPermissionIdentity(wm::reconnect, 933 android.Manifest.permission.NETWORK_SETTINGS); 934 } else { 935 SystemUtil.runShellCommand("svc wifi disable"); 936 } 937 938 tracker.waitForStateChange(); 939 940 assertTrue("Wifi must be " + (enable ? "connected to" : "disconnected from") 941 + " an access point for this test.", 942 enable == isWiFiConnected(cm, wm)); 943 944 cm.unregisterNetworkCallback(tracker); 945 } 946 } 947 isWiFiConnected(final ConnectivityManager cm, final WifiManager wm)948 static boolean isWiFiConnected(final ConnectivityManager cm, final WifiManager wm) { 949 if (!wm.isWifiEnabled()) { 950 return false; 951 } 952 final Network network = cm.getActiveNetwork(); 953 if (network == null) { 954 return false; 955 } 956 final NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(network); 957 return networkCapabilities != null && networkCapabilities.hasTransport(TRANSPORT_WIFI); 958 } 959 960 /** 961 * Disconnect from WiFi in an attempt to connect to cellular data. Worth noting that this is 962 * best effort - there are no public APIs to force connecting to cell data. We disable WiFi 963 * and wait for a broadcast that we're connected to cell. 964 * We will not call into this function if the device doesn't support telephony. 965 * @see #mHasTelephony 966 * @see #checkDeviceSupportsMobileData() 967 */ disconnectWifiToConnectToMobile()968 private void disconnectWifiToConnectToMobile() throws Exception { 969 setAirplaneMode(false); 970 if (mHasWifi && mWifiManager.isWifiEnabled()) { 971 NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build(); 972 NetworkCapabilities nc = new NetworkCapabilities.Builder() 973 .addTransportType(TRANSPORT_CELLULAR) 974 .build(); 975 NetworkTracker tracker = new NetworkTracker(nc, true, mCm); 976 mCm.registerNetworkCallback(nr, tracker); 977 978 disconnectFromWifi(); 979 980 assertTrue("Device must have access to a metered network for this test.", 981 tracker.waitForStateChange()); 982 983 mCm.unregisterNetworkCallback(tracker); 984 } 985 } 986 987 /** 988 * Ensures that restrict background data usage policy is turned off. 989 * If the policy is on, it interferes with tests that relies on metered connection. 990 */ setDataSaverEnabled(boolean enabled)991 private void setDataSaverEnabled(boolean enabled) throws Exception { 992 SystemUtil.runShellCommand(getInstrumentation(), 993 enabled ? RESTRICT_BACKGROUND_ON_CMD : RESTRICT_BACKGROUND_OFF_CMD); 994 } 995 isAirplaneModeOn()996 private boolean isAirplaneModeOn() throws Exception { 997 final String output = SystemUtil.runShellCommand(getInstrumentation(), 998 "cmd connectivity airplane-mode").trim(); 999 return "enabled".equals(output); 1000 } 1001 setAirplaneMode(boolean on)1002 private void setAirplaneMode(boolean on) throws Exception { 1003 if (isAirplaneModeOn() == on) { 1004 return; 1005 } 1006 final CallbackAsserter airplaneModeBroadcastAsserter = CallbackAsserter.forBroadcast( 1007 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); 1008 SystemUtil.runShellCommand(getInstrumentation(), 1009 "cmd connectivity airplane-mode " + (on ? "enable" : "disable")); 1010 airplaneModeBroadcastAsserter.assertCalled("Didn't get airplane mode changed broadcast", 1011 15 /* 15 seconds */); 1012 waitUntil("Networks didn't change to " + (!on ? " on" : " off"), 60 /* seconds */, 1013 () -> { 1014 if (on) { 1015 return mCm.getActiveNetwork() == null 1016 && (!mHasWifi || !isWiFiConnected(mCm, mWifiManager)); 1017 } else { 1018 return mCm.getActiveNetwork() != null; 1019 } 1020 }); 1021 // Wait some time for the network changes to propagate. Can't use 1022 // waitUntil(isAirplaneModeOn() == on) because the response quickly gives the new 1023 // airplane mode status even though the network changes haven't propagated all the way to 1024 // JobScheduler. 1025 Thread.sleep(5000); 1026 } 1027 1028 private static class NetworkTracker extends ConnectivityManager.NetworkCallback { 1029 private static final int MSG_CHECK_ACTIVE_NETWORK = 1; 1030 private final ConnectivityManager mCm; 1031 1032 private final CountDownLatch mReceiveLatch = new CountDownLatch(1); 1033 1034 private final NetworkCapabilities mExpectedCapabilities; 1035 1036 private final boolean mExpectedConnected; 1037 1038 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 1039 @Override 1040 public void handleMessage(Message msg) { 1041 if (msg.what == MSG_CHECK_ACTIVE_NETWORK) { 1042 checkActiveNetwork(); 1043 } 1044 } 1045 }; 1046 NetworkTracker(NetworkCapabilities expectedCapabilities, boolean expectedConnected, ConnectivityManager cm)1047 private NetworkTracker(NetworkCapabilities expectedCapabilities, boolean expectedConnected, 1048 ConnectivityManager cm) { 1049 mExpectedCapabilities = expectedCapabilities; 1050 mExpectedConnected = expectedConnected; 1051 mCm = cm; 1052 } 1053 1054 @Override onAvailable(Network network)1055 public void onAvailable(Network network) { 1056 // Available doesn't mean it's the active network. We need to check that separately. 1057 checkActiveNetwork(); 1058 } 1059 1060 @Override onLost(Network network)1061 public void onLost(Network network) { 1062 checkActiveNetwork(); 1063 } 1064 waitForStateChange()1065 boolean waitForStateChange() throws InterruptedException { 1066 checkActiveNetwork(); 1067 return mReceiveLatch.await(60, TimeUnit.SECONDS); 1068 } 1069 checkActiveNetwork()1070 private void checkActiveNetwork() { 1071 mHandler.removeMessages(MSG_CHECK_ACTIVE_NETWORK); 1072 if (mReceiveLatch.getCount() == 0) { 1073 return; 1074 } 1075 1076 Network activeNetwork = mCm.getActiveNetwork(); 1077 if (mExpectedConnected) { 1078 if (activeNetwork != null && mExpectedCapabilities.satisfiedByNetworkCapabilities( 1079 mCm.getNetworkCapabilities(activeNetwork))) { 1080 mReceiveLatch.countDown(); 1081 } else { 1082 mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000); 1083 } 1084 } else { 1085 if (activeNetwork == null 1086 || !mExpectedCapabilities.satisfiedByNetworkCapabilities( 1087 mCm.getNetworkCapabilities(activeNetwork))) { 1088 mReceiveLatch.countDown(); 1089 } else { 1090 mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000); 1091 } 1092 } 1093 } 1094 } 1095 } 1096