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