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.jobtestapp.TestJobService.ACTION_JOB_STARTED; 20 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED; 21 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY; 22 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; 23 import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED; 24 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assume.assumeTrue; 28 29 import android.app.job.JobParameters; 30 import android.content.BroadcastReceiver; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.jobscheduler.cts.jobtestapp.TestActivity; 36 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver; 37 import android.os.PowerManager; 38 import android.os.SystemClock; 39 import android.support.test.InstrumentationRegistry; 40 import android.support.test.filters.LargeTest; 41 import android.support.test.runner.AndroidJUnit4; 42 import android.support.test.uiautomator.UiDevice; 43 import android.util.Log; 44 45 import com.android.compatibility.common.util.AppStandbyUtils; 46 47 import org.junit.After; 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 52 /** 53 * Tests that temp whitelisted apps can run jobs if all the other constraints are met 54 */ 55 @RunWith(AndroidJUnit4.class) 56 @LargeTest 57 public class DeviceIdleJobsTest { 58 private static final String TAG = DeviceIdleJobsTest.class.getSimpleName(); 59 private static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp"; 60 private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver"; 61 private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity"; 62 private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000; 63 private static final long POLL_INTERVAL = 500; 64 private static final long DEFAULT_WAIT_TIMEOUT = 1000; 65 private static final long SHELL_TIMEOUT = 3_000; 66 67 enum Bucket { 68 ACTIVE, 69 WORKING_SET, 70 FREQUENT, 71 RARE, 72 NEVER 73 } 74 75 private Context mContext; 76 private UiDevice mUiDevice; 77 private PowerManager mPowerManager; 78 private long mTempWhitelistExpiryElapsed; 79 private int mTestJobId; 80 private int mTestPackageUid; 81 private boolean mDeviceInDoze; 82 private boolean mDeviceIdleEnabled; 83 private boolean mAppStandbyEnabled; 84 85 /* accesses must be synchronized on itself */ 86 private final TestJobStatus mTestJobStatus = new TestJobStatus(); 87 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 88 @Override 89 public void onReceive(Context context, Intent intent) { 90 Log.d(TAG, "Received action " + intent.getAction()); 91 switch (intent.getAction()) { 92 case ACTION_JOB_STARTED: 93 case ACTION_JOB_STOPPED: 94 final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY); 95 Log.d(TAG, "JobId: " + params.getJobId()); 96 synchronized (mTestJobStatus) { 97 mTestJobStatus.running = ACTION_JOB_STARTED.equals(intent.getAction()); 98 mTestJobStatus.jobId = params.getJobId(); 99 } 100 break; 101 case ACTION_DEVICE_IDLE_MODE_CHANGED: 102 case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED: 103 synchronized (DeviceIdleJobsTest.this) { 104 mDeviceInDoze = mPowerManager.isDeviceIdleMode(); 105 Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze); 106 } 107 break; 108 } 109 } 110 }; 111 isDeviceIdleEnabled(UiDevice uiDevice)112 private static boolean isDeviceIdleEnabled(UiDevice uiDevice) throws Exception { 113 final String output = uiDevice.executeShellCommand("cmd deviceidle enabled deep").trim(); 114 return Integer.parseInt(output) != 0; 115 } 116 117 @Before setUp()118 public void setUp() throws Exception { 119 mContext = InstrumentationRegistry.getTargetContext(); 120 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 121 mPowerManager = mContext.getSystemService(PowerManager.class); 122 mDeviceInDoze = mPowerManager.isDeviceIdleMode(); 123 mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0); 124 mTestJobId = (int) (SystemClock.uptimeMillis() / 1000); 125 mTestJobStatus.reset(); 126 mTempWhitelistExpiryElapsed = -1; 127 final IntentFilter intentFilter = new IntentFilter(); 128 intentFilter.addAction(ACTION_JOB_STARTED); 129 intentFilter.addAction(ACTION_JOB_STOPPED); 130 intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); 131 intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); 132 mContext.registerReceiver(mReceiver, intentFilter); 133 assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted()); 134 makeTestPackageIdle(); 135 mDeviceIdleEnabled = isDeviceIdleEnabled(mUiDevice); 136 mAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabled(); 137 if (mAppStandbyEnabled) { 138 setTestPackageStandbyBucket(Bucket.ACTIVE); 139 } else { 140 Log.w(TAG, "App standby not enabled on test device"); 141 } 142 } 143 144 @Test testAllowWhileIdleJobInTempwhitelist()145 public void testAllowWhileIdleJobInTempwhitelist() throws Exception { 146 assumeTrue("device idle not enabled", mDeviceIdleEnabled); 147 148 toggleDeviceIdleState(true); 149 Thread.sleep(DEFAULT_WAIT_TIMEOUT); 150 sendScheduleJobBroadcast(true); 151 assertFalse("Job started without being tempwhitelisted", awaitJobStart(5_000)); 152 tempWhitelistTestApp(5_000); 153 assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted", 154 awaitJobStart(DEFAULT_WAIT_TIMEOUT)); 155 } 156 157 @Test testForegroundJobsStartImmediately()158 public void testForegroundJobsStartImmediately() throws Exception { 159 assumeTrue("device idle not enabled", mDeviceIdleEnabled); 160 161 sendScheduleJobBroadcast(false); 162 assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); 163 toggleDeviceIdleState(true); 164 assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT)); 165 Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF); 166 startAndKeepTestActivity(); 167 toggleDeviceIdleState(false); 168 assertTrue("Job for foreground app did not start immediately when device exited doze", 169 awaitJobStart(3_000)); 170 } 171 172 @Test testBackgroundJobsDelayed()173 public void testBackgroundJobsDelayed() throws Exception { 174 assumeTrue("device idle not enabled", mDeviceIdleEnabled); 175 176 sendScheduleJobBroadcast(false); 177 assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); 178 toggleDeviceIdleState(true); 179 assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT)); 180 Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF); 181 toggleDeviceIdleState(false); 182 assertFalse("Job for background app started immediately when device exited doze", 183 awaitJobStart(DEFAULT_WAIT_TIMEOUT)); 184 Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT); 185 assertTrue("Job for background app did not start after the expected delay of " 186 + BACKGROUND_JOBS_EXPECTED_DELAY + "ms", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); 187 } 188 189 @Test testJobsInNeverApp()190 public void testJobsInNeverApp() throws Exception { 191 assumeTrue("app standby not enabled", mAppStandbyEnabled); 192 193 enterFakeUnpluggedState(); 194 setTestPackageStandbyBucket(Bucket.NEVER); 195 Thread.sleep(DEFAULT_WAIT_TIMEOUT); 196 sendScheduleJobBroadcast(false); 197 assertFalse("New job started in NEVER standby", awaitJobStart(3_000)); 198 resetFakeUnpluggedState(); 199 } 200 201 @Test testUidActiveBypassesStandby()202 public void testUidActiveBypassesStandby() throws Exception { 203 enterFakeUnpluggedState(); 204 setTestPackageStandbyBucket(Bucket.NEVER); 205 tempWhitelistTestApp(6_000); 206 Thread.sleep(DEFAULT_WAIT_TIMEOUT); 207 sendScheduleJobBroadcast(false); 208 assertTrue("New job in uid-active app failed to start in NEVER standby", 209 awaitJobStart(4_000)); 210 resetFakeUnpluggedState(); 211 } 212 213 @After tearDown()214 public void tearDown() throws Exception { 215 if (mDeviceIdleEnabled) { 216 toggleDeviceIdleState(false); 217 } 218 final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS); 219 cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER)); 220 cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 221 mContext.sendBroadcast(cancelJobsIntent); 222 mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY)); 223 mContext.unregisterReceiver(mReceiver); 224 Thread.sleep(500); // To avoid any race between unregister and the next register in setUp 225 waitUntilTestAppNotInTempWhitelist(); 226 } 227 isTestAppTempWhitelisted()228 private boolean isTestAppTempWhitelisted() throws Exception { 229 final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim(); 230 for (String line : output.split("\n")) { 231 if (line.contains("UID="+mTestPackageUid)) { 232 return true; 233 } 234 } 235 return false; 236 } 237 startAndKeepTestActivity()238 private void startAndKeepTestActivity() { 239 final Intent testActivity = new Intent(); 240 testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 241 testActivity.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); 242 mContext.startActivity(testActivity); 243 } 244 sendScheduleJobBroadcast(boolean allowWhileIdle)245 private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception { 246 final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB); 247 scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 248 scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mTestJobId); 249 scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle); 250 scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER)); 251 mContext.sendBroadcast(scheduleJobIntent); 252 } 253 toggleDeviceIdleState(final boolean idle)254 private void toggleDeviceIdleState(final boolean idle) throws Exception { 255 mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce")); 256 assertTrue("Could not change device idle state to " + idle, 257 waitUntilTrue(SHELL_TIMEOUT, () -> { 258 synchronized (DeviceIdleJobsTest.this) { 259 return mDeviceInDoze == idle; 260 } 261 })); 262 } 263 tempWhitelistTestApp(long duration)264 private void tempWhitelistTestApp(long duration) throws Exception { 265 mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration 266 + " " + TEST_APP_PACKAGE); 267 mTempWhitelistExpiryElapsed = SystemClock.elapsedRealtime() + duration; 268 } 269 makeTestPackageIdle()270 private void makeTestPackageIdle() throws Exception { 271 mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE); 272 } 273 setTestPackageStandbyBucket(Bucket bucket)274 private void setTestPackageStandbyBucket(Bucket bucket) throws Exception { 275 final String bucketName; 276 switch (bucket) { 277 case ACTIVE: bucketName = "active"; break; 278 case WORKING_SET: bucketName = "working"; break; 279 case FREQUENT: bucketName = "frequent"; break; 280 case RARE: bucketName = "rare"; break; 281 case NEVER: bucketName = "never"; break; 282 default: 283 throw new IllegalArgumentException("Requested unknown bucket " + bucket); 284 } 285 mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE 286 + " " + bucketName); 287 } 288 enterFakeUnpluggedState()289 private void enterFakeUnpluggedState() throws Exception { 290 mUiDevice.executeShellCommand("dumpsys battery unplug"); 291 } 292 resetFakeUnpluggedState()293 private void resetFakeUnpluggedState() throws Exception { 294 mUiDevice.executeShellCommand("dumpsys battery reset"); 295 } 296 waitUntilTestAppNotInTempWhitelist()297 private boolean waitUntilTestAppNotInTempWhitelist() throws Exception { 298 long now; 299 boolean interrupted = false; 300 while ((now = SystemClock.elapsedRealtime()) < mTempWhitelistExpiryElapsed) { 301 try { 302 Thread.sleep(mTempWhitelistExpiryElapsed - now); 303 } catch (InterruptedException iexc) { 304 interrupted = true; 305 } 306 } 307 if (interrupted) { 308 Thread.currentThread().interrupt(); 309 } 310 return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted()); 311 } 312 awaitJobStart(long maxWait)313 private boolean awaitJobStart(long maxWait) throws Exception { 314 return waitUntilTrue(maxWait, () -> { 315 synchronized (mTestJobStatus) { 316 return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running; 317 } 318 }); 319 } 320 321 private boolean awaitJobStop(long maxWait) throws Exception { 322 return waitUntilTrue(maxWait, () -> { 323 synchronized (mTestJobStatus) { 324 return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running; 325 } 326 }); 327 } 328 329 private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception { 330 final long deadLine = SystemClock.uptimeMillis() + maxWait; 331 do { 332 Thread.sleep(POLL_INTERVAL); 333 } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine); 334 return condition.isTrue(); 335 } 336 337 private static final class TestJobStatus { 338 int jobId; 339 boolean running; 340 private void reset() { 341 running = false; 342 } 343 } 344 345 private interface Condition { 346 boolean isTrue() throws Exception; 347 } 348 } 349