Home | History | Annotate | Download | only in cts
      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 
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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 
    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 
    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 
    352     private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
    353         mTestAppInterface.scheduleJob(allowWhileIdle, false);
    354     }
    355 
    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 
    366     private void tempWhitelistTestApp(long duration) throws Exception {
    367         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
    368                 + " " + TEST_APP_PACKAGE);
    369     }
    370 
    371     private void makeTestPackageIdle() throws Exception {
    372         mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
    373     }
    374 
    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 
    400     private boolean removeTestAppFromTempWhitelist() throws Exception {
    401         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -r " + TEST_APP_PACKAGE);
    402         return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted());
    403     }
    404 
    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 {
    414         boolean isTrue() throws Exception;
    415     }
    416 }
    417