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