1 /* 2 * Copyright (C) 2019 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.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 19 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD; 20 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; 21 import static android.app.ActivityManager.getCapabilitiesSummary; 22 import static android.app.ActivityManager.procStateToString; 23 import static android.jobscheduler.cts.BaseJobSchedulerTest.HW_TIMEOUT_MULTIPLIER; 24 import static android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver.ACTION_JOB_SCHEDULE_RESULT; 25 import static android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver.EXTRA_REQUEST_JOB_UID_STATE; 26 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STARTED; 27 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED; 28 import static android.jobscheduler.cts.jobtestapp.TestJobService.INVALID_ADJ; 29 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_CAPABILITIES_KEY; 30 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_OOM_SCORE_ADJ_KEY; 31 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY; 32 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PROC_STATE_KEY; 33 import static android.server.wm.WindowManagerState.STATE_RESUMED; 34 35 import static org.junit.Assert.assertEquals; 36 import static org.junit.Assert.assertTrue; 37 import static org.junit.Assert.fail; 38 39 import android.Manifest; 40 import android.app.ActivityManager; 41 import android.app.AppOpsManager; 42 import android.app.compat.CompatChanges; 43 import android.app.job.JobParameters; 44 import android.app.job.JobScheduler; 45 import android.content.BroadcastReceiver; 46 import android.content.ComponentName; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.IntentFilter; 50 import android.content.pm.PackageManager; 51 import android.jobscheduler.cts.jobtestapp.TestActivity; 52 import android.jobscheduler.cts.jobtestapp.TestFgsService; 53 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver; 54 import android.net.NetworkPolicyManager; 55 import android.os.SystemClock; 56 import android.os.UserHandle; 57 import android.server.wm.WindowManagerStateHelper; 58 import android.util.Log; 59 import android.util.SparseArray; 60 61 import com.android.compatibility.common.util.AppOpsUtils; 62 import com.android.compatibility.common.util.AppStandbyUtils; 63 import com.android.compatibility.common.util.CallbackAsserter; 64 import com.android.compatibility.common.util.SystemUtil; 65 66 import java.util.Collections; 67 import java.util.Map; 68 import java.util.Set; 69 import java.util.function.BooleanSupplier; 70 71 /** 72 * Common functions to interact with the test app. 73 */ 74 class TestAppInterface implements AutoCloseable { 75 private static final String TAG = TestAppInterface.class.getSimpleName(); 76 77 public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L; 78 79 static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp"; 80 private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity"; 81 private static final String TEST_APP_FGS = TEST_APP_PACKAGE + ".TestFgsService"; 82 static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver"; 83 84 private final Context mContext; 85 private final NetworkPolicyManager mNetworkPolicyManager; 86 private final int mJobId; 87 private final int mTestPackageUid; 88 89 /* accesses must be synchronized on itself */ 90 private final SparseArray<TestJobState> mTestJobStates = new SparseArray(); 91 TestAppInterface(Context ctx, int jobId)92 TestAppInterface(Context ctx, int jobId) { 93 mContext = ctx; 94 mJobId = jobId; 95 mNetworkPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); 96 97 try { 98 mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0); 99 } catch (PackageManager.NameNotFoundException e) { 100 throw new IllegalStateException("Test app uid not found", e); 101 } 102 103 final IntentFilter intentFilter = new IntentFilter(); 104 intentFilter.addAction(ACTION_JOB_STARTED); 105 intentFilter.addAction(ACTION_JOB_STOPPED); 106 intentFilter.addAction(ACTION_JOB_SCHEDULE_RESULT); 107 mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED); 108 SystemUtil.runShellCommand( 109 "am compat enable --no-kill ALLOW_TEST_API_ACCESS " + TEST_APP_PACKAGE); 110 if (AppStandbyUtils.isAppStandbyEnabled()) { 111 // Disable the bucket elevation so that we put the app in lower buckets. 112 SystemUtil.runShellCommand( 113 "am compat enable --no-kill SCHEDULE_EXACT_ALARM_DOES_NOT_ELEVATE_BUCKET " 114 + TEST_APP_PACKAGE); 115 // Force the test app out of the never bucket. 116 SystemUtil.runShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " rare"); 117 } 118 // Remove the app from the whitelist. 119 SystemUtil.runShellCommand("cmd deviceidle whitelist -" + TEST_APP_PACKAGE); 120 SystemUtil.runShellCommand("cmd netpolicy start-watching " + mTestPackageUid); 121 if (isTestAppTempWhitelisted()) { 122 Log.w(TAG, "Test package already in temp whitelist"); 123 if (!removeTestAppFromTempWhitelist()) { 124 // Don't block the test, but log in case it's an issue. 125 Log.w(TAG, "Test package wasn't removed from the temp whitelist"); 126 } 127 } 128 } 129 cleanup()130 void cleanup() throws Exception { 131 final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS); 132 cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER)); 133 cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 134 mContext.sendBroadcast(cancelJobsIntent); 135 closeActivity(); 136 stopFgs(); 137 mContext.unregisterReceiver(mReceiver); 138 AppOpsUtils.reset(TEST_APP_PACKAGE); 139 SystemUtil.runWithShellPermissionIdentity( 140 () -> CompatChanges.removePackageOverrides( 141 TestAppInterface.TEST_APP_PACKAGE, 142 Set.of(ENFORCE_MINIMUM_TIME_WINDOWS)), 143 OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD, INTERACT_ACROSS_USERS_FULL); 144 SystemUtil.runShellCommand("am compat reset-all " + TEST_APP_PACKAGE); 145 // Remove the app from the whitelist. 146 SystemUtil.runShellCommand("cmd deviceidle whitelist -" + TEST_APP_PACKAGE); 147 removeTestAppFromTempWhitelist(); 148 mTestJobStates.clear(); 149 SystemUtil.runShellCommand("cmd netpolicy stop-watching"); 150 SystemUtil.runShellCommand( 151 "cmd jobscheduler reset-execution-quota -u " + UserHandle.myUserId() + " " 152 + TEST_APP_PACKAGE); 153 forceStopApp(); // Clean up as much internal/temporary system state as possible 154 } 155 156 @Override close()157 public void close() throws Exception { 158 cleanup(); 159 } 160 scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob)161 void scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob) 162 throws Exception { 163 scheduleJob(allowWhileIdle, requiredNetworkType, asExpeditedJob, false); 164 } 165 scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob, boolean asUserInitiatedJob)166 void scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob, 167 boolean asUserInitiatedJob) throws Exception { 168 scheduleJob( 169 Map.of( 170 TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle, 171 TestJobSchedulerReceiver.EXTRA_AS_EXPEDITED, asExpeditedJob, 172 TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, asUserInitiatedJob 173 ), 174 Map.of( 175 TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, requiredNetworkType 176 )); 177 } 178 generateScheduleJobIntent(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras, Map<String, Long> longExtras)179 private Intent generateScheduleJobIntent(Map<String, Boolean> booleanExtras, 180 Map<String, Integer> intExtras, Map<String, Long> longExtras) { 181 final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB); 182 scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 183 if (!intExtras.containsKey(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY)) { 184 scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mJobId); 185 } 186 booleanExtras.forEach(scheduleJobIntent::putExtra); 187 intExtras.forEach(scheduleJobIntent::putExtra); 188 longExtras.forEach(scheduleJobIntent::putExtra); 189 scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER)); 190 return scheduleJobIntent; 191 } 192 scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)193 void scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras) 194 throws Exception { 195 scheduleJob(booleanExtras, intExtras, Collections.emptyMap()); 196 } 197 scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras, Map<String, Long> longExtras)198 void scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras, 199 Map<String, Long> longExtras) throws Exception { 200 final Intent scheduleJobIntent = 201 generateScheduleJobIntent(booleanExtras, intExtras, longExtras); 202 203 final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast( 204 new IntentFilter(TestJobSchedulerReceiver.ACTION_JOB_SCHEDULE_RESULT)); 205 mContext.sendBroadcast(scheduleJobIntent); 206 resultBroadcastAsserter.assertCalled("Didn't get schedule job result broadcast", 207 15 /* 15 seconds */); 208 } 209 postUiInitiatingNotification(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)210 void postUiInitiatingNotification(Map<String, Boolean> booleanExtras, 211 Map<String, Integer> intExtras) throws Exception { 212 final Intent intent = 213 generateScheduleJobIntent(booleanExtras, intExtras, Collections.emptyMap()); 214 intent.setAction(TestJobSchedulerReceiver.ACTION_POST_UI_INITIATING_NOTIFICATION); 215 216 final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast( 217 new IntentFilter(TestJobSchedulerReceiver.ACTION_NOTIFICATION_POSTED)); 218 mContext.sendBroadcast(intent); 219 resultBroadcastAsserter.assertCalled("Didn't get notification posted broadcast", 220 15 /* 15 seconds */); 221 } 222 223 /** Post an alarm that will start an FGS in the test app. */ postFgsStartingAlarm()224 void postFgsStartingAlarm() throws Exception { 225 AppOpsUtils.setOpMode(TEST_APP_PACKAGE, 226 AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, AppOpsManager.MODE_ALLOWED); 227 final Intent intent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_FGS_START_ALARM); 228 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 229 intent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER)); 230 231 final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast( 232 new IntentFilter(TestJobSchedulerReceiver.ACTION_ALARM_SCHEDULED)); 233 mContext.sendBroadcast(intent); 234 resultBroadcastAsserter.assertCalled("Didn't get alarm scheduled broadcast", 235 15 /* 15 seconds */); 236 } 237 238 /** Asks (not forces) JobScheduler to run the job if constraints are met. */ runSatisfiedJob()239 void runSatisfiedJob() throws Exception { 240 runSatisfiedJob(mJobId); 241 } 242 kill()243 void kill() { 244 SystemUtil.runShellCommand("am stop-app " + TEST_APP_PACKAGE); 245 mTestJobStates.clear(); 246 } 247 isNetworkBlockedByPolicy()248 boolean isNetworkBlockedByPolicy() { 249 try { 250 return SystemUtil.callWithShellPermissionIdentity( 251 () -> mNetworkPolicyManager.isUidNetworkingBlocked(mTestPackageUid, false), 252 Manifest.permission.OBSERVE_NETWORK_POLICY); 253 } catch (Exception e) { 254 // Unexpected while calling isUidNetworkingBlocked. 255 throw new RuntimeException(e); 256 } 257 } 258 runSatisfiedJob(int jobId)259 void runSatisfiedJob(int jobId) throws Exception { 260 if (HW_TIMEOUT_MULTIPLIER > 1) { 261 // Device has increased HW multiplier. Wait a short amount of time before sending the 262 // run command since there's a higher chance JobScheduler's processing is delayed. 263 Thread.sleep(1_000L); 264 } 265 SystemUtil.runShellCommand("cmd jobscheduler run -s" 266 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + jobId); 267 } 268 269 /** Forces JobScheduler to run the job */ forceRunJob()270 void forceRunJob() throws Exception { 271 SystemUtil.runShellCommand("cmd jobscheduler run -f" 272 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mJobId); 273 } 274 stopJob(int stopReason, int internalStopReason)275 void stopJob(int stopReason, int internalStopReason) throws Exception { 276 SystemUtil.runShellCommand("cmd jobscheduler stop" 277 + " -u " + UserHandle.myUserId() 278 + " -s " + stopReason + " -i " + internalStopReason 279 + " " + TEST_APP_PACKAGE + " " + mJobId); 280 } 281 forceStopApp()282 void forceStopApp() { 283 SystemUtil.runShellCommand("am force-stop" 284 + " --user " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE); 285 } 286 setTestPackageRestricted(boolean restricted)287 void setTestPackageRestricted(boolean restricted) throws Exception { 288 AppOpsUtils.setOpMode(TEST_APP_PACKAGE, "RUN_ANY_IN_BACKGROUND", 289 restricted ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED); 290 } 291 cancelJob()292 void cancelJob() throws Exception { 293 SystemUtil.runShellCommand("cmd jobscheduler cancel" 294 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mJobId); 295 } 296 startAndKeepTestActivity()297 void startAndKeepTestActivity() { 298 startAndKeepTestActivity(false); 299 } 300 startAndKeepTestActivity(boolean waitForResume)301 void startAndKeepTestActivity(boolean waitForResume) { 302 final Intent testActivity = new Intent(); 303 testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 304 ComponentName testComponentName = new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY); 305 testActivity.setComponent(testComponentName); 306 mContext.startActivity(testActivity); 307 if (waitForResume) { 308 new WindowManagerStateHelper().waitForActivityState(testComponentName, STATE_RESUMED); 309 } 310 } 311 closeActivity()312 void closeActivity() { 313 closeActivity(false); 314 } 315 closeActivity(boolean waitForClose)316 void closeActivity(boolean waitForClose) { 317 mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY)); 318 if (waitForClose) { 319 ComponentName testComponentName = 320 new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY); 321 new WindowManagerStateHelper().waitForActivityRemoved(testComponentName); 322 } 323 } 324 isTestAppTempWhitelisted()325 boolean isTestAppTempWhitelisted() { 326 final String output = SystemUtil.runShellCommand("cmd deviceidle tempwhitelist").trim(); 327 final String expectedText = "UID=" + UserHandle.getAppId(mTestPackageUid); 328 for (String line : output.split("\n")) { 329 if (line.contains(expectedText)) { 330 return true; 331 } 332 } 333 return false; 334 } 335 removeTestAppFromTempWhitelist()336 boolean removeTestAppFromTempWhitelist() { 337 SystemUtil.runShellCommand("cmd deviceidle tempwhitelist" 338 + " -u " + UserHandle.myUserId() 339 + " -r " + TEST_APP_PACKAGE); 340 final boolean removed = waitUntilTrue(3_000, () -> !isTestAppTempWhitelisted()); 341 if (!removed) { 342 Log.e(TAG, "Test app wasn't removed from temp whitelist"); 343 } 344 return removed; 345 } 346 347 /** Directly start the FGS in the test app. */ startFgs()348 void startFgs() throws Exception { 349 final Intent intent = new Intent(TestJobSchedulerReceiver.ACTION_START_FGS); 350 intent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER)); 351 352 final CallbackAsserter resultBroadcastAsserter = 353 CallbackAsserter.forBroadcast(new IntentFilter(TestFgsService.ACTION_FGS_STARTED)); 354 mContext.sendBroadcast(intent); 355 resultBroadcastAsserter.assertCalled("Didn't get FGS started broadcast", 356 15 /* 15 seconds */); 357 } 358 stopFgs()359 void stopFgs() { 360 final Intent testFgs = new Intent(TestFgsService.ACTION_STOP_FOREGROUND); 361 mContext.sendBroadcast(testFgs); 362 } 363 364 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 365 @Override 366 public void onReceive(Context context, Intent intent) { 367 Log.d(TAG, "Received action " + intent.getAction()); 368 switch (intent.getAction()) { 369 case ACTION_JOB_STARTED: 370 case ACTION_JOB_STOPPED: 371 final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY); 372 Log.d(TAG, "JobId: " + params.getJobId()); 373 synchronized (mTestJobStates) { 374 TestJobState jobState = mTestJobStates.get(params.getJobId()); 375 if (jobState == null) { 376 jobState = new TestJobState(); 377 mTestJobStates.put(params.getJobId(), jobState); 378 } else { 379 jobState.reset(); 380 } 381 jobState.running = ACTION_JOB_STARTED.equals(intent.getAction()); 382 jobState.params = params; 383 // With these broadcasts, the job is/was running, and therefore scheduling 384 // was successful. 385 jobState.scheduleResult = JobScheduler.RESULT_SUCCESS; 386 if (intent.getBooleanExtra(EXTRA_REQUEST_JOB_UID_STATE, false)) { 387 jobState.procState = intent.getIntExtra(JOB_PROC_STATE_KEY, 388 ActivityManager.PROCESS_STATE_NONEXISTENT); 389 jobState.capabilities = intent.getIntExtra(JOB_CAPABILITIES_KEY, 390 ActivityManager.PROCESS_CAPABILITY_NONE); 391 jobState.oomScoreAdj = intent.getIntExtra(JOB_OOM_SCORE_ADJ_KEY, 392 INVALID_ADJ); 393 } 394 } 395 break; 396 case ACTION_JOB_SCHEDULE_RESULT: 397 synchronized (mTestJobStates) { 398 final int jobId = intent.getIntExtra( 399 TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, 0); 400 TestJobState jobState = mTestJobStates.get(jobId); 401 if (jobState == null) { 402 jobState = new TestJobState(); 403 mTestJobStates.put(jobId, jobState); 404 } else { 405 jobState.reset(); 406 } 407 jobState.running = false; 408 jobState.params = null; 409 jobState.scheduleResult = intent.getIntExtra( 410 TestJobSchedulerReceiver.EXTRA_SCHEDULE_RESULT, -1); 411 } 412 break; 413 } 414 } 415 }; 416 awaitJobStart(long maxWait)417 boolean awaitJobStart(long maxWait) throws Exception { 418 return awaitJobStart(mJobId, maxWait); 419 } 420 awaitJobStart(int jobId, long maxWait)421 boolean awaitJobStart(int jobId, long maxWait) throws Exception { 422 return waitUntilTrue(maxWait, () -> { 423 synchronized (mTestJobStates) { 424 TestJobState jobState = mTestJobStates.get(jobId); 425 return jobState != null && jobState.running; 426 } 427 }); 428 } 429 430 boolean awaitJobStop(long maxWait) throws Exception { 431 return waitUntilTrue(maxWait, () -> { 432 synchronized (mTestJobStates) { 433 TestJobState jobState = mTestJobStates.get(mJobId); 434 return jobState != null && !jobState.running; 435 } 436 }); 437 } 438 439 private String getJobState(int jobId) throws Exception { 440 return SystemUtil.runShellCommand( 441 "cmd jobscheduler get-job-state --user cur " + TEST_APP_PACKAGE + " " + jobId) 442 .trim(); 443 } 444 445 void assertJobNotReady(int jobId) throws Exception { 446 String state = getJobState(jobId); 447 assertTrue("Job unexpectedly ready, in state: " + state, !state.contains("ready")); 448 } 449 450 void assertJobUidState(ExpectedJobUidState expected) { 451 synchronized (mTestJobStates) { 452 TestJobState jobState = mTestJobStates.get(mJobId); 453 if (jobState == null) { 454 fail("Job not started"); 455 } 456 assertEquals("procState expected=" + procStateToString(expected.procState) 457 + ",actual=" + procStateToString(jobState.procState), 458 expected.procState, jobState.procState); 459 assertEquals( 460 "capabilities expected=" + getCapabilitiesSummary(expected.includedCapability) 461 + ",actual=" + getCapabilitiesSummary(jobState.capabilities), 462 expected.includedCapability, 463 jobState.capabilities & expected.includedCapability); 464 assertEquals( 465 "capabilities unexpected=" + getCapabilitiesSummary(expected.excludedCapability) 466 + ",actual=" + getCapabilitiesSummary(jobState.capabilities), 467 0, jobState.capabilities & expected.excludedCapability); 468 assertEquals("Unexpected oomScoreAdj", expected.oomScoreAdj, jobState.oomScoreAdj); 469 } 470 } 471 472 boolean awaitJobScheduleResult(long maxWaitMs, int jobResult) throws Exception { 473 return awaitJobScheduleResult(mJobId, maxWaitMs, jobResult); 474 } 475 476 boolean awaitJobScheduleResult(int jobId, long maxWaitMs, int jobResult) throws Exception { 477 return waitUntilTrue(maxWaitMs, () -> { 478 synchronized (mTestJobStates) { 479 TestJobState jobState = mTestJobStates.get(jobId); 480 return jobState != null && jobState.scheduleResult == jobResult; 481 } 482 }); 483 } 484 485 private boolean waitUntilTrue(long maxWait, BooleanSupplier condition) { 486 final long deadline = SystemClock.uptimeMillis() + maxWait; 487 do { 488 SystemClock.sleep(500); 489 } while (!condition.getAsBoolean() && SystemClock.uptimeMillis() < deadline); 490 return condition.getAsBoolean(); 491 } 492 493 JobParameters getLastParams() { 494 synchronized (mTestJobStates) { 495 TestJobState jobState = mTestJobStates.get(mJobId); 496 return jobState == null ? null : jobState.params; 497 } 498 } 499 500 private static final class TestJobState { 501 int scheduleResult; 502 boolean running; 503 int procState; 504 int capabilities; 505 int oomScoreAdj; 506 JobParameters params; 507 508 TestJobState() { 509 initState(); 510 } 511 512 private void reset() { 513 initState(); 514 } 515 516 private void initState() { 517 running = false; 518 procState = ActivityManager.PROCESS_STATE_NONEXISTENT; 519 capabilities = ActivityManager.PROCESS_CAPABILITY_NONE; 520 oomScoreAdj = INVALID_ADJ; 521 scheduleResult = -1; 522 } 523 } 524 525 public static final class ExpectedJobUidState { 526 public final int procState; 527 public final int oomScoreAdj; 528 public final int includedCapability; 529 public final int excludedCapability; 530 531 private ExpectedJobUidState(Builder builder) { 532 procState = builder.mProcState; 533 oomScoreAdj = builder.mOomScoreAdj; 534 includedCapability = builder.mIncludedCapability; 535 excludedCapability = builder.mExcludedCapability; 536 } 537 538 public static class Builder { 539 int mProcState = PROCESS_STATE_UNKNOWN; 540 int mOomScoreAdj = INVALID_ADJ; 541 int mIncludedCapability = 0; 542 int mExcludedCapability = 0; 543 544 Builder setProcState(int procState) { 545 mProcState = procState; 546 return this; 547 } 548 549 Builder setOomScoreAdj(int oomScoreAdj) { 550 mOomScoreAdj = oomScoreAdj; 551 return this; 552 } 553 554 Builder setExpectedCapability(int capability) { 555 mIncludedCapability = capability; 556 return this; 557 } 558 559 Builder setUnexpectedCapability(int capability) { 560 mExcludedCapability = capability; 561 return this; 562 } 563 564 ExpectedJobUidState build() { 565 return new ExpectedJobUidState(this); 566 } 567 } 568 } 569 } 570