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