1 package com.android.server.job; 2 3 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; 4 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; 5 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 6 7 import static org.junit.Assert.assertArrayEquals; 8 import static org.junit.Assert.assertEquals; 9 import static org.junit.Assert.assertFalse; 10 import static org.junit.Assert.assertNotNull; 11 import static org.junit.Assert.assertNull; 12 import static org.junit.Assert.assertThrows; 13 import static org.junit.Assert.assertTrue; 14 import static org.junit.Assert.fail; 15 import static org.mockito.ArgumentMatchers.anyString; 16 import static org.mockito.Mockito.mock; 17 import static org.mockito.Mockito.when; 18 19 import android.app.job.JobInfo; 20 import android.app.job.JobInfo.Builder; 21 import android.app.job.JobWorkItem; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.pm.PackageManagerInternal; 25 import android.net.NetworkRequest; 26 import android.os.Build; 27 import android.os.PersistableBundle; 28 import android.os.SystemClock; 29 import android.test.RenamingDelegatingContext; 30 import android.util.ArraySet; 31 import android.util.Log; 32 import android.util.Pair; 33 34 import androidx.test.InstrumentationRegistry; 35 import androidx.test.filters.SmallTest; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import com.android.internal.util.ArrayUtils; 39 import com.android.server.LocalServices; 40 import com.android.server.job.JobStore.JobSet; 41 import com.android.server.job.controllers.JobStatus; 42 43 import org.junit.After; 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 import java.io.File; 49 import java.time.Clock; 50 import java.time.ZoneOffset; 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Objects; 54 import java.util.Set; 55 56 /** 57 * Test reading and writing correctly from file. 58 * 59 * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java 60 */ 61 @RunWith(AndroidJUnit4.class) 62 @SmallTest 63 public class JobStoreTest { 64 private static final String TAG = "TaskStoreTest"; 65 private static final String TEST_PREFIX = "_test_"; 66 67 private static final int SOME_UID = android.os.Process.FIRST_APPLICATION_UID; 68 private ComponentName mComponent; 69 70 JobStore mTaskStoreUnderTest; 71 Context mTestContext; 72 getContext()73 private Context getContext() { 74 return InstrumentationRegistry.getContext(); 75 } 76 77 @Before setUp()78 public void setUp() throws Exception { 79 mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX); 80 Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'"); 81 mTaskStoreUnderTest = 82 JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir()); 83 mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName()); 84 85 // Assume all packages are current SDK 86 final PackageManagerInternal pm = mock(PackageManagerInternal.class); 87 when(pm.getPackageTargetSdkVersion(anyString())) 88 .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT); 89 LocalServices.removeServiceForTest(PackageManagerInternal.class); 90 LocalServices.addService(PackageManagerInternal.class, pm); 91 92 // Freeze the clocks at this moment in time 93 JobSchedulerService.sSystemClock = 94 Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); 95 JobSchedulerService.sUptimeMillisClock = 96 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC); 97 JobSchedulerService.sElapsedRealtimeClock = 98 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); 99 } 100 101 @After tearDown()102 public void tearDown() throws Exception { 103 mTaskStoreUnderTest.clear(); 104 mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L); 105 } 106 setUseSplitFiles(boolean useSplitFiles)107 private void setUseSplitFiles(boolean useSplitFiles) throws Exception { 108 mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles); 109 waitForPendingIo(); 110 } 111 waitForPendingIo()112 private void waitForPendingIo() throws Exception { 113 assertTrue("Timed out waiting for persistence I/O to complete", 114 mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L)); 115 } 116 117 /** Test that we properly remove the last job of an app from the persisted file. */ 118 @Test testRemovingLastJob_singleFile()119 public void testRemovingLastJob_singleFile() throws Exception { 120 setUseSplitFiles(false); 121 runRemovingLastJob(); 122 } 123 124 /** Test that we properly remove the last job of an app from the persisted file. */ 125 @Test testRemovingLastJob_splitFiles()126 public void testRemovingLastJob_splitFiles() throws Exception { 127 setUseSplitFiles(true); 128 runRemovingLastJob(); 129 } 130 runRemovingLastJob()131 private void runRemovingLastJob() throws Exception { 132 final JobInfo task1 = new Builder(8, mComponent) 133 .setRequiresDeviceIdle(true) 134 .setPeriodic(10000L) 135 .setRequiresCharging(true) 136 .setPersisted(true) 137 .build(); 138 final JobInfo task2 = new Builder(12, mComponent) 139 .setMinimumLatency(5000L) 140 .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) 141 .setOverrideDeadline(30000L) 142 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 143 .setPersisted(true) 144 .build(); 145 final int uid1 = SOME_UID; 146 final int uid2 = uid1 + 1; 147 final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null); 148 final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null); 149 runWritingJobsToDisk(JobStatus1, JobStatus2); 150 151 // Remove 1 job 152 mTaskStoreUnderTest.remove(JobStatus1, true); 153 waitForPendingIo(); 154 JobSet jobStatusSet = new JobSet(); 155 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 156 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 157 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 158 159 assertJobsEqual(JobStatus2, loaded); 160 assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2)); 161 162 // Remove 2nd job 163 mTaskStoreUnderTest.remove(JobStatus2, true); 164 waitForPendingIo(); 165 jobStatusSet = new JobSet(); 166 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 167 assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); 168 } 169 170 /** Test that we properly clear the persisted file when all jobs are dropped. */ 171 @Test testClearJobs_singleFile()172 public void testClearJobs_singleFile() throws Exception { 173 setUseSplitFiles(false); 174 runClearJobs(); 175 } 176 177 /** Test that we properly clear the persisted file when all jobs are dropped. */ 178 @Test testClearJobs_splitFiles()179 public void testClearJobs_splitFiles() throws Exception { 180 setUseSplitFiles(true); 181 runClearJobs(); 182 } 183 runClearJobs()184 private void runClearJobs() throws Exception { 185 final JobInfo task1 = new Builder(8, mComponent) 186 .setRequiresDeviceIdle(true) 187 .setPeriodic(10000L) 188 .setRequiresCharging(true) 189 .setPersisted(true) 190 .build(); 191 final JobInfo task2 = new Builder(12, mComponent) 192 .setMinimumLatency(5000L) 193 .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) 194 .setOverrideDeadline(30000L) 195 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 196 .setPersisted(true) 197 .build(); 198 final int uid1 = SOME_UID; 199 final int uid2 = uid1 + 1; 200 final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null); 201 final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null); 202 runWritingJobsToDisk(JobStatus1, JobStatus2); 203 204 // Remove all jobs 205 mTaskStoreUnderTest.clear(); 206 waitForPendingIo(); 207 JobSet jobStatusSet = new JobSet(); 208 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 209 assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); 210 } 211 212 /** 213 * Test that dynamic constraints aren't written to disk. 214 */ 215 @Test testDynamicConstraintsNotPersisted()216 public void testDynamicConstraintsNotPersisted() throws Exception { 217 JobInfo.Builder b = new Builder(42, mComponent).setPersisted(true); 218 JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); 219 js.addDynamicConstraints(JobStatus.CONSTRAINT_BATTERY_NOT_LOW 220 | JobStatus.CONSTRAINT_CHARGING 221 | JobStatus.CONSTRAINT_IDLE 222 | JobStatus.CONSTRAINT_STORAGE_NOT_LOW); 223 assertTrue(js.hasBatteryNotLowConstraint()); 224 assertTrue(js.hasChargingConstraint()); 225 assertTrue(js.hasIdleConstraint()); 226 assertTrue(js.hasStorageNotLowConstraint()); 227 mTaskStoreUnderTest.add(js); 228 waitForPendingIo(); 229 230 final JobSet jobStatusSet = new JobSet(); 231 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 232 assertEquals("Job count is incorrect.", 1, jobStatusSet.size()); 233 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 234 assertFalse(loaded.hasBatteryNotLowConstraint()); 235 assertFalse(loaded.hasChargingConstraint()); 236 assertFalse(loaded.hasIdleConstraint()); 237 assertFalse(loaded.hasStorageNotLowConstraint()); 238 } 239 240 @Test testExtractUidFromJobFileName()241 public void testExtractUidFromJobFileName() { 242 File file = new File(mTestContext.getFilesDir(), "randomName"); 243 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 244 245 file = new File(mTestContext.getFilesDir(), "jobs.xml"); 246 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 247 248 file = new File(mTestContext.getFilesDir(), ".xml"); 249 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 250 251 file = new File(mTestContext.getFilesDir(), "1000.xml"); 252 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 253 254 file = new File(mTestContext.getFilesDir(), "10000"); 255 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 256 257 file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX); 258 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 259 260 file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml"); 261 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 262 263 file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml"); 264 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 265 266 file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml"); 267 assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); 268 269 file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml"); 270 assertEquals(1, JobStore.extractUidFromJobFileName(file)); 271 272 file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml"); 273 assertEquals(101023, JobStore.extractUidFromJobFileName(file)); 274 } 275 276 @Test testStringToIntArrayAndIntArrayToString()277 public void testStringToIntArrayAndIntArrayToString() { 278 final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 }; 279 final String netCapabilitiesStr = "1,3,5,7,9"; 280 final String netCapabilitiesStrWithErrorInt = "1,3,a,7,9"; 281 final String emptyString = ""; 282 final String str1 = JobStore.intArrayToString(netCapabilitiesIntArray); 283 assertArrayEquals(netCapabilitiesIntArray, JobStore.stringToIntArray(str1)); 284 assertEquals(0, JobStore.stringToIntArray(emptyString).length); 285 assertThrows(NumberFormatException.class, 286 () -> JobStore.stringToIntArray(netCapabilitiesStrWithErrorInt)); 287 assertEquals(netCapabilitiesStr, JobStore.intArrayToString(netCapabilitiesIntArray)); 288 } 289 290 @Test testMaybeWriteStatusToDisk()291 public void testMaybeWriteStatusToDisk() throws Exception { 292 int taskId = 5; 293 long runByMillis = 20000L; // 20s 294 long runFromMillis = 2000L; // 2s 295 long initialBackoff = 10000L; // 10s 296 297 final JobInfo task = new Builder(taskId, mComponent) 298 .setRequiresCharging(true) 299 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 300 .setBackoffCriteria(initialBackoff, JobInfo.BACKOFF_POLICY_EXPONENTIAL) 301 .setOverrideDeadline(runByMillis) 302 .setMinimumLatency(runFromMillis) 303 .setPersisted(true) 304 .build(); 305 final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null, null); 306 ts.addInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION); 307 mTaskStoreUnderTest.add(ts); 308 waitForPendingIo(); 309 310 // Manually load tasks from xml file. 311 final JobSet jobStatusSet = new JobSet(); 312 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 313 314 assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size()); 315 final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0); 316 assertJobsEqual(ts, loadedTaskStatus); 317 assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts)); 318 } 319 320 @Test testWritingTwoJobsToDisk_singleFile()321 public void testWritingTwoJobsToDisk_singleFile() throws Exception { 322 setUseSplitFiles(false); 323 runWritingTwoJobsToDisk(); 324 } 325 326 @Test testWritingTwoJobsToDisk_splitFiles()327 public void testWritingTwoJobsToDisk_splitFiles() throws Exception { 328 setUseSplitFiles(true); 329 runWritingTwoJobsToDisk(); 330 } 331 runWritingTwoJobsToDisk()332 private void runWritingTwoJobsToDisk() throws Exception { 333 final JobInfo task1 = new Builder(8, mComponent) 334 .setRequiresDeviceIdle(true) 335 .setPeriodic(10000L) 336 .setRequiresCharging(true) 337 .setPersisted(true) 338 .build(); 339 final JobInfo task2 = new Builder(12, mComponent) 340 .setMinimumLatency(5000L) 341 .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) 342 .setOverrideDeadline(30000L) 343 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 344 .setPersisted(true) 345 .build(); 346 final int uid1 = SOME_UID; 347 final int uid2 = uid1 + 1; 348 final JobStatus taskStatus1 = 349 JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null); 350 final JobStatus taskStatus2 = 351 JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null); 352 353 runWritingJobsToDisk(taskStatus1, taskStatus2); 354 } 355 runWritingJobsToDisk(JobStatus... jobStatuses)356 private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception { 357 ArraySet<JobStatus> expectedJobs = new ArraySet<>(); 358 for (JobStatus jobStatus : jobStatuses) { 359 mTaskStoreUnderTest.add(jobStatus); 360 expectedJobs.add(jobStatus); 361 } 362 waitForPendingIo(); 363 364 final JobSet jobStatusSet = new JobSet(); 365 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 366 assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size()); 367 int count = 0; 368 final int expectedCount = expectedJobs.size(); 369 for (JobStatus loaded : jobStatusSet.getAllJobs()) { 370 count++; 371 for (int i = 0; i < expectedJobs.size(); ++i) { 372 JobStatus expected = expectedJobs.valueAt(i); 373 374 try { 375 assertJobsEqual(expected, loaded); 376 expectedJobs.remove(expected); 377 break; 378 } catch (AssertionError e) { 379 // Not equal. Move along. 380 } 381 } 382 } 383 assertEquals("Loaded more jobs than expected", expectedCount, count); 384 if (expectedJobs.size() > 0) { 385 fail("Not all expected jobs were restored"); 386 } 387 for (JobStatus jobStatus : jobStatuses) { 388 assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus)); 389 } 390 } 391 392 @Test testWritingTaskWithExtras()393 public void testWritingTaskWithExtras() throws Exception { 394 JobInfo.Builder b = new Builder(8, mComponent) 395 .setRequiresDeviceIdle(true) 396 .setPeriodic(10000L) 397 .setRequiresCharging(true) 398 .setPersisted(true); 399 400 PersistableBundle extras = new PersistableBundle(); 401 extras.putDouble("hello", 3.2); 402 extras.putString("hi", "there"); 403 extras.putInt("into", 3); 404 b.setExtras(extras); 405 final JobInfo task = b.build(); 406 JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null, null); 407 408 mTaskStoreUnderTest.add(taskStatus); 409 waitForPendingIo(); 410 411 final JobSet jobStatusSet = new JobSet(); 412 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 413 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 414 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 415 assertJobsEqual(taskStatus, loaded); 416 } 417 418 @Test testWritingTaskWithSourcePackage()419 public void testWritingTaskWithSourcePackage() throws Exception { 420 JobInfo.Builder b = new Builder(8, mComponent) 421 .setRequiresDeviceIdle(true) 422 .setPeriodic(10000L) 423 .setRequiresCharging(true) 424 .setPersisted(true); 425 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, 426 "com.android.test.app", 0, null, null); 427 428 mTaskStoreUnderTest.add(taskStatus); 429 waitForPendingIo(); 430 431 final JobSet jobStatusSet = new JobSet(); 432 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 433 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 434 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 435 assertEquals("Source package not equal.", loaded.getSourcePackageName(), 436 taskStatus.getSourcePackageName()); 437 assertEquals("Source user not equal.", loaded.getSourceUserId(), 438 taskStatus.getSourceUserId()); 439 } 440 441 @Test testWritingTaskWithFlex()442 public void testWritingTaskWithFlex() throws Exception { 443 JobInfo.Builder b = new Builder(8, mComponent) 444 .setRequiresDeviceIdle(true) 445 .setPeriodic(5*60*60*1000, 1*60*60*1000) 446 .setRequiresCharging(true) 447 .setPersisted(true); 448 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), 449 SOME_UID, null, -1, null, null); 450 451 mTaskStoreUnderTest.add(taskStatus); 452 waitForPendingIo(); 453 454 final JobSet jobStatusSet = new JobSet(); 455 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 456 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 457 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 458 assertEquals("Period not equal.", loaded.getJob().getIntervalMillis(), 459 taskStatus.getJob().getIntervalMillis()); 460 assertEquals("Flex not equal.", loaded.getJob().getFlexMillis(), 461 taskStatus.getJob().getFlexMillis()); 462 } 463 464 @Test testMassivePeriodClampedOnRead()465 public void testMassivePeriodClampedOnRead() throws Exception { 466 final long ONE_HOUR = 60*60*1000L; // flex 467 final long TWO_HOURS = 2 * ONE_HOUR; // period 468 JobInfo.Builder b = new Builder(8, mComponent) 469 .setPeriodic(TWO_HOURS, ONE_HOUR) 470 .setPersisted(true); 471 final long rtcNow = System.currentTimeMillis(); 472 final long invalidLateRuntimeElapsedMillis = 473 SystemClock.elapsedRealtime() + (TWO_HOURS * ONE_HOUR) + TWO_HOURS; // > period+flex 474 final long invalidEarlyRuntimeElapsedMillis = 475 invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period). 476 final Pair<Long, Long> persistedExecutionTimesUTC = new Pair<>(rtcNow, rtcNow + ONE_HOUR); 477 final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 478 0 /* sourceUserId */, 0, "someNamespace", "someTag", 479 invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, 480 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, 481 0 /* cumulativeExecutionTime */, 482 persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */); 483 484 mTaskStoreUnderTest.add(js); 485 waitForPendingIo(); 486 487 final JobSet jobStatusSet = new JobSet(); 488 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 489 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 490 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 491 492 // Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll 493 // call SystemClock.elapsedRealtime after doing the disk i/o. 494 final long newNowElapsed = SystemClock.elapsedRealtime(); 495 assertTrue("Early runtime wasn't correctly clamped.", 496 loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS); 497 // Assert late runtime was clamped to be now + period + flex. 498 assertTrue("Early runtime wasn't correctly clamped.", 499 loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS + ONE_HOUR); 500 } 501 502 @Test testBiasPersisted()503 public void testBiasPersisted() throws Exception { 504 JobInfo.Builder b = new Builder(92, mComponent) 505 .setOverrideDeadline(5000) 506 .setBias(42) 507 .setPersisted(true); 508 final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); 509 mTaskStoreUnderTest.add(js); 510 waitForPendingIo(); 511 512 final JobSet jobStatusSet = new JobSet(); 513 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 514 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 515 assertEquals("Bias not correctly persisted.", 42, loaded.getBias()); 516 } 517 518 @Test testCumulativeExecutionTimePersisted()519 public void testCumulativeExecutionTimePersisted() throws Exception { 520 JobInfo ji = new Builder(53, mComponent).setPersisted(true).build(); 521 final JobStatus js = JobStatus.createFromJobInfo(ji, SOME_UID, null, -1, null, null); 522 js.incrementCumulativeExecutionTime(1234567890); 523 mTaskStoreUnderTest.add(js); 524 waitForPendingIo(); 525 526 final JobSet jobStatusSet = new JobSet(); 527 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 528 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 529 assertEquals("Cumulative execution time not correctly persisted.", 530 1234567890, loaded.getCumulativeExecutionTimeMs()); 531 } 532 533 @Test testNamespacePersisted()534 public void testNamespacePersisted() throws Exception { 535 final String namespace = "my.test.namespace"; 536 JobInfo.Builder b = new Builder(93, mComponent) 537 .setRequiresBatteryNotLow(true) 538 .setPersisted(true); 539 final JobStatus js = 540 JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, namespace, null); 541 mTaskStoreUnderTest.add(js); 542 waitForPendingIo(); 543 544 final JobSet jobStatusSet = new JobSet(); 545 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 546 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 547 assertEquals("Namespace not correctly persisted.", namespace, loaded.getNamespace()); 548 } 549 550 @Test testPriorityPersisted()551 public void testPriorityPersisted() throws Exception { 552 final JobInfo job = new Builder(92, mComponent) 553 .setOverrideDeadline(5000) 554 .setPriority(JobInfo.PRIORITY_MIN) 555 .setPersisted(true) 556 .build(); 557 final JobStatus js = JobStatus.createFromJobInfo(job, SOME_UID, null, -1, null, null); 558 mTaskStoreUnderTest.add(js); 559 waitForPendingIo(); 560 561 final JobSet jobStatusSet = new JobSet(); 562 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 563 final JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 564 assertEquals("Priority not correctly persisted.", 565 JobInfo.PRIORITY_MIN, job.getPriority()); 566 } 567 568 /** 569 * Test that non persisted job is not written to disk. 570 */ 571 @Test testNonPersistedTaskIsNotPersisted()572 public void testNonPersistedTaskIsNotPersisted() throws Exception { 573 JobInfo.Builder b = new Builder(42, mComponent) 574 .setOverrideDeadline(10000) 575 .setPersisted(false); 576 JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(), 577 SOME_UID, null, -1, null, null); 578 mTaskStoreUnderTest.add(jsNonPersisted); 579 b = new Builder(43, mComponent) 580 .setOverrideDeadline(10000) 581 .setPersisted(true); 582 JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), 583 SOME_UID, null, -1, null, null); 584 mTaskStoreUnderTest.add(jsPersisted); 585 waitForPendingIo(); 586 587 final JobSet jobStatusSet = new JobSet(); 588 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 589 assertEquals("Job count is incorrect.", 1, jobStatusSet.size()); 590 JobStatus jobStatus = jobStatusSet.getAllJobs().iterator().next(); 591 assertEquals("Wrong job persisted.", 43, jobStatus.getJobId()); 592 } 593 594 @Test testRequiredNetworkType()595 public void testRequiredNetworkType() throws Exception { 596 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 597 .setPersisted(true) 598 .setRequiresDeviceIdle(true) 599 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE).build()); 600 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 601 .setPersisted(true) 602 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build()); 603 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 604 .setPersisted(true) 605 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED).build()); 606 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 607 .setPersisted(true) 608 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING).build()); 609 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 610 .setPersisted(true) 611 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR).build()); 612 } 613 614 @Test testRequiredNetwork()615 public void testRequiredNetwork() throws Exception { 616 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 617 .setPersisted(true) 618 .setRequiresDeviceIdle(true) 619 .setRequiredNetwork(null).build()); 620 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 621 .setPersisted(true) 622 .setRequiredNetwork(new NetworkRequest.Builder().build()).build()); 623 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 624 .setPersisted(true) 625 .setRequiredNetwork(new NetworkRequest.Builder() 626 .addTransportType(TRANSPORT_WIFI).build()) 627 .build()); 628 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 629 .setPersisted(true) 630 .setRequiredNetwork(new NetworkRequest.Builder() 631 .addCapability(NET_CAPABILITY_IMS) 632 .addForbiddenCapability(NET_CAPABILITY_OEM_PAID) 633 .build()) 634 .build()); 635 } 636 637 @Test testEstimatedNetworkBytes()638 public void testEstimatedNetworkBytes() throws Exception { 639 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 640 .setPersisted(true) 641 .setRequiredNetwork(new NetworkRequest.Builder().build()) 642 .setEstimatedNetworkBytes( 643 JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN) 644 .build()); 645 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 646 .setPersisted(true) 647 .setRequiredNetwork(new NetworkRequest.Builder().build()) 648 .setEstimatedNetworkBytes(5, 15) 649 .build()); 650 } 651 652 @Test testMinimumNetworkChunkBytes()653 public void testMinimumNetworkChunkBytes() throws Exception { 654 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 655 .setPersisted(true) 656 .setRequiredNetwork(new NetworkRequest.Builder().build()) 657 .setMinimumNetworkChunkBytes(JobInfo.NETWORK_BYTES_UNKNOWN) 658 .build()); 659 assertPersistedEquals(new JobInfo.Builder(0, mComponent) 660 .setPersisted(true) 661 .setRequiredNetwork(new NetworkRequest.Builder().build()) 662 .setMinimumNetworkChunkBytes(42) 663 .build()); 664 } 665 666 @Test testPersistedIdleConstraint()667 public void testPersistedIdleConstraint() throws Exception { 668 JobInfo.Builder b = new Builder(8, mComponent) 669 .setRequiresDeviceIdle(true) 670 .setPersisted(true); 671 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), 672 SOME_UID, null, -1, null, null); 673 674 mTaskStoreUnderTest.add(taskStatus); 675 waitForPendingIo(); 676 677 final JobSet jobStatusSet = new JobSet(); 678 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 679 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 680 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 681 assertEquals("Idle constraint not persisted correctly.", 682 loaded.getJob().isRequireDeviceIdle(), 683 taskStatus.getJob().isRequireDeviceIdle()); 684 } 685 686 @Test testPersistedChargingConstraint()687 public void testPersistedChargingConstraint() throws Exception { 688 JobInfo.Builder b = new Builder(8, mComponent) 689 .setRequiresCharging(true) 690 .setPersisted(true); 691 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), 692 SOME_UID, null, -1, null, null); 693 694 mTaskStoreUnderTest.add(taskStatus); 695 waitForPendingIo(); 696 697 final JobSet jobStatusSet = new JobSet(); 698 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 699 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 700 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 701 assertEquals("Charging constraint not persisted correctly.", 702 loaded.getJob().isRequireCharging(), 703 taskStatus.getJob().isRequireCharging()); 704 } 705 706 @Test testPersistedStorageNotLowConstraint()707 public void testPersistedStorageNotLowConstraint() throws Exception { 708 JobInfo.Builder b = new Builder(8, mComponent) 709 .setRequiresStorageNotLow(true) 710 .setPersisted(true); 711 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), 712 SOME_UID, null, -1, null, null); 713 714 mTaskStoreUnderTest.add(taskStatus); 715 waitForPendingIo(); 716 717 final JobSet jobStatusSet = new JobSet(); 718 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 719 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 720 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 721 assertEquals("Storage-not-low constraint not persisted correctly.", 722 loaded.getJob().isRequireStorageNotLow(), 723 taskStatus.getJob().isRequireStorageNotLow()); 724 } 725 726 @Test testPersistedBatteryNotLowConstraint()727 public void testPersistedBatteryNotLowConstraint() throws Exception { 728 JobInfo.Builder b = new Builder(8, mComponent) 729 .setRequiresBatteryNotLow(true) 730 .setPersisted(true); 731 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), 732 SOME_UID, null, -1, null, null); 733 734 mTaskStoreUnderTest.add(taskStatus); 735 waitForPendingIo(); 736 737 final JobSet jobStatusSet = new JobSet(); 738 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 739 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 740 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 741 assertEquals("Battery-not-low constraint not persisted correctly.", 742 loaded.getJob().isRequireBatteryNotLow(), 743 taskStatus.getJob().isRequireBatteryNotLow()); 744 } 745 746 @Test testPersistedPreferredBatteryNotLowConstraint()747 public void testPersistedPreferredBatteryNotLowConstraint() throws Exception { 748 JobInfo.Builder b = new Builder(8, mComponent) 749 .setPrefersBatteryNotLow(true) 750 .setPersisted(true); 751 JobStatus taskStatus = 752 JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); 753 754 mTaskStoreUnderTest.add(taskStatus); 755 waitForPendingIo(); 756 757 final JobSet jobStatusSet = new JobSet(); 758 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 759 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 760 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 761 assertEquals("Battery-not-low constraint not persisted correctly.", 762 taskStatus.getJob().isPreferBatteryNotLow(), 763 loaded.getJob().isPreferBatteryNotLow()); 764 } 765 766 @Test testPersistedPreferredChargingConstraint()767 public void testPersistedPreferredChargingConstraint() throws Exception { 768 JobInfo.Builder b = new Builder(8, mComponent) 769 .setPrefersCharging(true) 770 .setPersisted(true); 771 JobStatus taskStatus = 772 JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); 773 774 mTaskStoreUnderTest.add(taskStatus); 775 waitForPendingIo(); 776 777 final JobSet jobStatusSet = new JobSet(); 778 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 779 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 780 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 781 assertEquals("Charging constraint not persisted correctly.", 782 taskStatus.getJob().isPreferCharging(), 783 loaded.getJob().isPreferCharging()); 784 } 785 786 @Test testPersistedPreferredDeviceIdleConstraint()787 public void testPersistedPreferredDeviceIdleConstraint() throws Exception { 788 JobInfo.Builder b = new Builder(8, mComponent) 789 .setPrefersDeviceIdle(true) 790 .setPersisted(true); 791 JobStatus taskStatus = 792 JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); 793 794 mTaskStoreUnderTest.add(taskStatus); 795 waitForPendingIo(); 796 797 final JobSet jobStatusSet = new JobSet(); 798 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 799 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 800 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 801 assertEquals("Idle constraint not persisted correctly.", 802 taskStatus.getJob().isPreferDeviceIdle(), 803 loaded.getJob().isPreferDeviceIdle()); 804 } 805 806 @Test testJobWorkItems()807 public void testJobWorkItems() throws Exception { 808 JobWorkItem item1 = new JobWorkItem.Builder().build(); 809 item1.bumpDeliveryCount(); 810 PersistableBundle bundle = new PersistableBundle(); 811 bundle.putBoolean("test", true); 812 JobWorkItem item2 = new JobWorkItem.Builder().setExtras(bundle).build(); 813 item2.bumpDeliveryCount(); 814 JobWorkItem item3 = new JobWorkItem.Builder().setEstimatedNetworkBytes(1, 2).build(); 815 JobWorkItem item4 = new JobWorkItem.Builder().setMinimumNetworkChunkBytes(3).build(); 816 JobWorkItem item5 = new JobWorkItem.Builder().build(); 817 818 JobInfo jobInfo = new JobInfo.Builder(0, mComponent) 819 .setPersisted(true) 820 .build(); 821 JobStatus jobStatus = 822 JobStatus.createFromJobInfo(jobInfo, SOME_UID, null, -1, null, null); 823 jobStatus.executingWork = new ArrayList<>(List.of(item1, item2)); 824 jobStatus.pendingWork = new ArrayList<>(List.of(item3, item4, item5)); 825 assertPersistedEquals(jobStatus); 826 } 827 828 /** 829 * Helper function to kick a {@link JobInfo} through a persistence cycle and 830 * assert that it's unchanged. 831 */ assertPersistedEquals(JobInfo firstInfo)832 private void assertPersistedEquals(JobInfo firstInfo) throws Exception { 833 assertPersistedEquals( 834 JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null)); 835 } 836 assertPersistedEquals(JobStatus original)837 private void assertPersistedEquals(JobStatus original) throws Exception { 838 mTaskStoreUnderTest.clear(); 839 mTaskStoreUnderTest.add(original); 840 waitForPendingIo(); 841 842 final JobSet jobStatusSet = new JobSet(); 843 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); 844 final JobStatus second = jobStatusSet.getAllJobs().iterator().next(); 845 assertJobsEqual(original, second); 846 } 847 848 /** 849 * Helper function to throw an error if the provided JobStatus objects are not equal. 850 */ assertJobsEqual(JobStatus expected, JobStatus actual)851 private void assertJobsEqual(JobStatus expected, JobStatus actual) { 852 assertEquals(expected.getJob(), actual.getJob()); 853 854 // Source UID isn't persisted, but the rest of the app info is. 855 assertEquals("Source package not equal", 856 expected.getSourcePackageName(), actual.getSourcePackageName()); 857 assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId()); 858 assertEquals("Calling UID not equal", expected.getUid(), actual.getUid()); 859 assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId()); 860 861 assertEquals(expected.getNamespace(), actual.getNamespace()); 862 863 assertEquals("Internal flags not equal", 864 expected.getInternalFlags(), actual.getInternalFlags()); 865 866 // Check that the loaded task has the correct runtimes. 867 compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", 868 expected.getEarliestRunTime(), actual.getEarliestRunTime()); 869 compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", 870 expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed()); 871 872 assertEquals(expected.getCumulativeExecutionTimeMs(), 873 actual.getCumulativeExecutionTimeMs()); 874 875 assertEquals(expected.hasWorkLocked(), actual.hasWorkLocked()); 876 if (expected.hasWorkLocked()) { 877 List<JobWorkItem> allWork = new ArrayList<>(); 878 if (expected.executingWork != null) { 879 allWork.addAll(expected.executingWork); 880 } 881 if (expected.pendingWork != null) { 882 allWork.addAll(expected.pendingWork); 883 } 884 // All work for freshly loaded Job will be pending. 885 assertNotNull(actual.pendingWork); 886 assertTrue(ArrayUtils.isEmpty(actual.executingWork)); 887 assertEquals(allWork.size(), actual.pendingWork.size()); 888 for (int i = 0; i < allWork.size(); ++i) { 889 JobWorkItem expectedItem = allWork.get(i); 890 JobWorkItem actualItem = actual.pendingWork.get(i); 891 assertJobWorkItemsEqual(expectedItem, actualItem); 892 } 893 } 894 } 895 assertJobWorkItemsEqual(JobWorkItem expected, JobWorkItem actual)896 private void assertJobWorkItemsEqual(JobWorkItem expected, JobWorkItem actual) { 897 if (expected == null) { 898 assertNull(actual); 899 return; 900 } 901 assertNotNull(actual); 902 assertEquals(expected.getDeliveryCount(), actual.getDeliveryCount()); 903 assertEquals(expected.getEstimatedNetworkDownloadBytes(), 904 actual.getEstimatedNetworkDownloadBytes()); 905 assertEquals(expected.getEstimatedNetworkUploadBytes(), 906 actual.getEstimatedNetworkUploadBytes()); 907 assertEquals(expected.getMinimumNetworkChunkBytes(), actual.getMinimumNetworkChunkBytes()); 908 if (expected.getIntent() == null) { 909 assertNull(actual.getIntent()); 910 } else { 911 // filterEquals() just so happens to check almost everything that is persisted to disk. 912 assertTrue(expected.getIntent().filterEquals(actual.getIntent())); 913 assertEquals(expected.getIntent().getFlags(), actual.getIntent().getFlags()); 914 } 915 assertEquals(expected.getGrants(), actual.getGrants()); 916 PersistableBundle expectedExtras = expected.getExtras(); 917 PersistableBundle actualExtras = actual.getExtras(); 918 if (expectedExtras == null) { 919 assertNull(actualExtras); 920 } else { 921 assertEquals(expectedExtras.size(), actualExtras.size()); 922 Set<String> keys = expectedExtras.keySet(); 923 for (String key : keys) { 924 assertTrue(Objects.equals(expectedExtras.get(key), actualExtras.get(key))); 925 } 926 } 927 } 928 929 /** 930 * When comparing timestamps before and after DB read/writes (to make sure we're saving/loading 931 * the correct values), there is some latency involved that terrorises a naive assertEquals(). 932 * We define a <code>DELTA_MILLIS</code> as a function variable here to make this comparision 933 * more reasonable. 934 */ compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2)935 private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) { 936 final long DELTA_MILLIS = 700L; // We allow up to 700ms of latency for IO read/writes. 937 assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS); 938 } 939 940 private static class StubClass {} 941 } 942