1 /*
2  * Copyright (C) 2017 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 
17 package android.jobscheduler.cts;
18 
19 import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
20 import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
21 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
22 import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
23 
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.app.AppOpsManager;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.PackageManager;
34 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
35 import android.net.ConnectivityManager;
36 import android.net.wifi.WifiManager;
37 import android.os.PowerManager;
38 import android.os.SystemClock;
39 import android.os.Temperature;
40 import android.support.test.uiautomator.UiDevice;
41 import android.util.Log;
42 
43 import androidx.test.InstrumentationRegistry;
44 import androidx.test.filters.LargeTest;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.compatibility.common.util.AppOpsUtils;
48 import com.android.compatibility.common.util.AppStandbyUtils;
49 import com.android.compatibility.common.util.BatteryUtils;
50 import com.android.compatibility.common.util.ThermalUtils;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 
57 /**
58  * Tests related to job throttling -- device idle, app standby and battery saver.
59  */
60 @RunWith(AndroidJUnit4.class)
61 @LargeTest
62 public class JobThrottlingTest {
63     private static final String TAG = JobThrottlingTest.class.getSimpleName();
64     private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000;
65     private static final long POLL_INTERVAL = 500;
66     private static final long DEFAULT_WAIT_TIMEOUT = 1000;
67     private static final long SHELL_TIMEOUT = 3_000;
68 
69     enum Bucket {
70         ACTIVE,
71         WORKING_SET,
72         FREQUENT,
73         RARE,
74         NEVER
75     }
76 
77     private Context mContext;
78     private UiDevice mUiDevice;
79     private PowerManager mPowerManager;
80     private int mTestPackageUid;
81     private boolean mDeviceInDoze;
82     private boolean mDeviceIdleEnabled;
83     private boolean mAppStandbyEnabled;
84     private WifiManager mWifiManager;
85     private ConnectivityManager mCm;
86     /** Whether the device running these tests supports WiFi. */
87     private boolean mHasWifi;
88     /** Track whether WiFi was enabled in case we turn it off. */
89     private boolean mInitialWiFiState;
90 
91     private TestAppInterface mTestAppInterface;
92 
93     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
94         @Override
95         public void onReceive(Context context, Intent intent) {
96             Log.d(TAG, "Received action " + intent.getAction());
97             switch (intent.getAction()) {
98                 case ACTION_DEVICE_IDLE_MODE_CHANGED:
99                 case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
100                     synchronized (JobThrottlingTest.this) {
101                         mDeviceInDoze = mPowerManager.isDeviceIdleMode();
102                         Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
103                     }
104                     break;
105             }
106         }
107     };
108 
isDeviceIdleEnabled(UiDevice uiDevice)109     private static boolean isDeviceIdleEnabled(UiDevice uiDevice) throws Exception {
110         final String output = uiDevice.executeShellCommand("cmd deviceidle enabled deep").trim();
111         return Integer.parseInt(output) != 0;
112     }
113 
114     @Before
setUp()115     public void setUp() throws Exception {
116         mContext = InstrumentationRegistry.getTargetContext();
117         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
118         mPowerManager = mContext.getSystemService(PowerManager.class);
119         mDeviceInDoze = mPowerManager.isDeviceIdleMode();
120         mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
121         int testJobId = (int) (SystemClock.uptimeMillis() / 1000);
122         mTestAppInterface = new TestAppInterface(mContext, testJobId);
123         final IntentFilter intentFilter = new IntentFilter();
124         intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
125         intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
126         mContext.registerReceiver(mReceiver, intentFilter);
127         assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
128         makeTestPackageIdle();
129         mDeviceIdleEnabled = isDeviceIdleEnabled(mUiDevice);
130         mAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabled();
131         if (mAppStandbyEnabled) {
132             setTestPackageStandbyBucket(Bucket.ACTIVE);
133         } else {
134             Log.w(TAG, "App standby not enabled on test device");
135         }
136         mWifiManager = mContext.getSystemService(WifiManager.class);
137         mCm = mContext.getSystemService(ConnectivityManager.class);
138         mHasWifi = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
139         mInitialWiFiState = mWifiManager.isWifiEnabled();
140     }
141 
142     @Test
testAllowWhileIdleJobInTempwhitelist()143     public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
144         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
145 
146         toggleDeviceIdleState(true);
147         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
148         sendScheduleJobBroadcast(true);
149         assertFalse("Job started without being tempwhitelisted",
150                 mTestAppInterface.awaitJobStart(5_000));
151         tempWhitelistTestApp(5_000);
152         assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted",
153                 mTestAppInterface.awaitJobStart(5_000));
154     }
155 
156     @Test
testForegroundJobsStartImmediately()157     public void testForegroundJobsStartImmediately() throws Exception {
158         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
159 
160         sendScheduleJobBroadcast(false);
161         assertTrue("Job did not start after scheduling",
162                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
163         toggleDeviceIdleState(true);
164         assertTrue("Job did not stop on entering doze",
165                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
166         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
167         mTestAppInterface.startAndKeepTestActivity();
168         toggleDeviceIdleState(false);
169         assertTrue("Job for foreground app did not start immediately when device exited doze",
170                 mTestAppInterface.awaitJobStart(3_000));
171     }
172 
173     @Test
testBackgroundJobsDelayed()174     public void testBackgroundJobsDelayed() throws Exception {
175         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
176 
177         sendScheduleJobBroadcast(false);
178         assertTrue("Job did not start after scheduling",
179                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
180         toggleDeviceIdleState(true);
181         assertTrue("Job did not stop on entering doze",
182                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
183         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
184         toggleDeviceIdleState(false);
185         assertFalse("Job for background app started immediately when device exited doze",
186                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
187         Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
188         assertTrue("Job for background app did not start after the expected delay of "
189                         + BACKGROUND_JOBS_EXPECTED_DELAY + "ms",
190                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
191     }
192 
193     @Test
testJobStoppedWhenRestricted()194     public void testJobStoppedWhenRestricted() throws Exception {
195         sendScheduleJobBroadcast(false);
196         assertTrue("Job did not start after scheduling",
197                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
198         setTestPackageRestricted(true);
199         assertTrue("Job did not stop after test app was restricted",
200                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
201     }
202 
203     @Test
testRestrictedJobStartedWhenUnrestricted()204     public void testRestrictedJobStartedWhenUnrestricted() throws Exception {
205         setTestPackageRestricted(true);
206         sendScheduleJobBroadcast(false);
207         assertFalse("Job started for restricted app",
208                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
209         setTestPackageRestricted(false);
210         assertTrue("Job did not start when app was unrestricted",
211                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
212     }
213 
214     @Test
testRestrictedJobAllowedWhenUidActive()215     public void testRestrictedJobAllowedWhenUidActive() throws Exception {
216         setTestPackageRestricted(true);
217         sendScheduleJobBroadcast(false);
218         assertFalse("Job started for restricted app",
219                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
220         mTestAppInterface.startAndKeepTestActivity();
221         assertTrue("Job did not start when app had an activity",
222                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
223     }
224 
225     @Test
testBackgroundConnectivityJobsThrottled()226     public void testBackgroundConnectivityJobsThrottled() throws Exception {
227         if (!mHasWifi) {
228             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
229             return;
230         }
231         setWifiState(true, mContext, mCm, mWifiManager);
232         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
233         mTestAppInterface.scheduleJob(false, true);
234         assertTrue("Job did not start after scheduling",
235                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
236         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
237         assertTrue("Job did not stop on thermal throttling",
238                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
239         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
240         ThermalUtils.overrideThermalNotThrottling();
241         assertTrue("Job did not start back from throttling",
242                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
243     }
244 
245     @Test
testJobsInNeverApp()246     public void testJobsInNeverApp() throws Exception {
247         assumeTrue("app standby not enabled", mAppStandbyEnabled);
248 
249         BatteryUtils.runDumpsysBatteryUnplug();
250         setTestPackageStandbyBucket(Bucket.NEVER);
251         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
252         sendScheduleJobBroadcast(false);
253         assertFalse("New job started in NEVER standby", mTestAppInterface.awaitJobStart(3_000));
254     }
255 
256     @Test
testUidActiveBypassesStandby()257     public void testUidActiveBypassesStandby() throws Exception {
258         BatteryUtils.runDumpsysBatteryUnplug();
259         setTestPackageStandbyBucket(Bucket.NEVER);
260         tempWhitelistTestApp(6_000);
261         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
262         sendScheduleJobBroadcast(false);
263         assertTrue("New job in uid-active app failed to start in NEVER standby",
264                 mTestAppInterface.awaitJobStart(4_000));
265     }
266 
267     @Test
testBatterySaverOff()268     public void testBatterySaverOff() throws Exception {
269         BatteryUtils.assumeBatterySaverFeature();
270 
271         BatteryUtils.runDumpsysBatteryUnplug();
272         BatteryUtils.enableBatterySaver(false);
273         sendScheduleJobBroadcast(false);
274         assertTrue("New job failed to start with battery saver OFF",
275                 mTestAppInterface.awaitJobStart(3_000));
276     }
277 
278     @Test
testBatterySaverOn()279     public void testBatterySaverOn() throws Exception {
280         BatteryUtils.assumeBatterySaverFeature();
281 
282         BatteryUtils.runDumpsysBatteryUnplug();
283         BatteryUtils.enableBatterySaver(true);
284         sendScheduleJobBroadcast(false);
285         assertFalse("New job started with battery saver ON",
286                 mTestAppInterface.awaitJobStart(3_000));
287     }
288 
289     @Test
testUidActiveBypassesBatterySaverOn()290     public void testUidActiveBypassesBatterySaverOn() throws Exception {
291         BatteryUtils.assumeBatterySaverFeature();
292 
293         BatteryUtils.runDumpsysBatteryUnplug();
294         BatteryUtils.enableBatterySaver(true);
295         tempWhitelistTestApp(6_000);
296         sendScheduleJobBroadcast(false);
297         assertTrue("New job in uid-active app failed to start with battery saver OFF",
298                 mTestAppInterface.awaitJobStart(3_000));
299     }
300 
301     @Test
testBatterySaverOnThenUidActive()302     public void testBatterySaverOnThenUidActive() throws Exception {
303         BatteryUtils.assumeBatterySaverFeature();
304 
305         // Enable battery saver, and schedule a job. It shouldn't run.
306         BatteryUtils.runDumpsysBatteryUnplug();
307         BatteryUtils.enableBatterySaver(true);
308         sendScheduleJobBroadcast(false);
309         assertFalse("New job started with battery saver ON",
310                 mTestAppInterface.awaitJobStart(3_000));
311 
312 
313         // Then make the UID active. Now the job should run.
314         tempWhitelistTestApp(120_000);
315         assertTrue("New job in uid-active app failed to start with battery saver OFF",
316                 mTestAppInterface.awaitJobStart(120_000));
317     }
318 
319     @After
tearDown()320     public void tearDown() throws Exception {
321         AppOpsUtils.reset(TEST_APP_PACKAGE);
322         // Lock thermal service to not throttling
323         ThermalUtils.overrideThermalNotThrottling();
324         if (mDeviceIdleEnabled) {
325             toggleDeviceIdleState(false);
326         }
327         mTestAppInterface.cleanup();
328         BatteryUtils.runDumpsysBatteryReset();
329         removeTestAppFromTempWhitelist();
330 
331         // Ensure that we leave WiFi in its previous state.
332         if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
333             setWifiState(mInitialWiFiState, mContext, mCm, mWifiManager);
334         }
335     }
336 
setTestPackageRestricted(boolean restricted)337     private void setTestPackageRestricted(boolean restricted) throws Exception {
338         AppOpsUtils.setOpMode(TEST_APP_PACKAGE, "RUN_ANY_IN_BACKGROUND",
339                 restricted ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED);
340     }
341 
isTestAppTempWhitelisted()342     private boolean isTestAppTempWhitelisted() throws Exception {
343         final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
344         for (String line : output.split("\n")) {
345             if (line.contains("UID=" + mTestPackageUid)) {
346                 return true;
347             }
348         }
349         return false;
350     }
351 
sendScheduleJobBroadcast(boolean allowWhileIdle)352     private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
353         mTestAppInterface.scheduleJob(allowWhileIdle, false);
354     }
355 
toggleDeviceIdleState(final boolean idle)356     private void toggleDeviceIdleState(final boolean idle) throws Exception {
357         mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
358         assertTrue("Could not change device idle state to " + idle,
359                 waitUntilTrue(SHELL_TIMEOUT, () -> {
360                     synchronized (JobThrottlingTest.this) {
361                         return mDeviceInDoze == idle;
362                     }
363                 }));
364     }
365 
tempWhitelistTestApp(long duration)366     private void tempWhitelistTestApp(long duration) throws Exception {
367         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
368                 + " " + TEST_APP_PACKAGE);
369     }
370 
makeTestPackageIdle()371     private void makeTestPackageIdle() throws Exception {
372         mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
373     }
374 
setTestPackageStandbyBucket(Bucket bucket)375     private void setTestPackageStandbyBucket(Bucket bucket) throws Exception {
376         final String bucketName;
377         switch (bucket) {
378             case ACTIVE:
379                 bucketName = "active";
380                 break;
381             case WORKING_SET:
382                 bucketName = "working";
383                 break;
384             case FREQUENT:
385                 bucketName = "frequent";
386                 break;
387             case RARE:
388                 bucketName = "rare";
389                 break;
390             case NEVER:
391                 bucketName = "never";
392                 break;
393             default:
394                 throw new IllegalArgumentException("Requested unknown bucket " + bucket);
395         }
396         mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE
397                 + " " + bucketName);
398     }
399 
removeTestAppFromTempWhitelist()400     private boolean removeTestAppFromTempWhitelist() throws Exception {
401         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -r " + TEST_APP_PACKAGE);
402         return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted());
403     }
404 
waitUntilTrue(long maxWait, Condition condition)405     private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
406         final long deadLine = SystemClock.uptimeMillis() + maxWait;
407         do {
408             Thread.sleep(POLL_INTERVAL);
409         } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
410         return condition.isTrue();
411     }
412 
413     private interface Condition {
isTrue()414         boolean isTrue() throws Exception;
415     }
416 }
417