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