1 package com.android.server.job; 2 3 4 import android.content.ComponentName; 5 import android.content.Context; 6 import android.app.job.JobInfo; 7 import android.app.job.JobInfo.Builder; 8 import android.os.PersistableBundle; 9 import android.os.SystemClock; 10 import android.test.AndroidTestCase; 11 import android.test.RenamingDelegatingContext; 12 import android.util.Log; 13 import android.util.ArraySet; 14 15 import com.android.server.job.JobStore.JobSet; 16 import com.android.server.job.controllers.JobStatus; 17 18 import java.util.Iterator; 19 20 /** 21 * Test reading and writing correctly from file. 22 */ 23 public class JobStoreTest extends AndroidTestCase { 24 private static final String TAG = "TaskStoreTest"; 25 private static final String TEST_PREFIX = "_test_"; 26 27 private static final int SOME_UID = 34234; 28 private ComponentName mComponent; 29 private static final long IO_WAIT = 1000L; 30 31 JobStore mTaskStoreUnderTest; 32 Context mTestContext; 33 34 @Override setUp()35 public void setUp() throws Exception { 36 mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX); 37 Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'"); 38 mTaskStoreUnderTest = 39 JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir()); 40 mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName()); 41 } 42 43 @Override tearDown()44 public void tearDown() throws Exception { 45 mTaskStoreUnderTest.clear(); 46 } 47 testMaybeWriteStatusToDisk()48 public void testMaybeWriteStatusToDisk() throws Exception { 49 int taskId = 5; 50 long runByMillis = 20000L; // 20s 51 long runFromMillis = 2000L; // 2s 52 long initialBackoff = 10000L; // 10s 53 54 final JobInfo task = new Builder(taskId, mComponent) 55 .setRequiresCharging(true) 56 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 57 .setBackoffCriteria(initialBackoff, JobInfo.BACKOFF_POLICY_EXPONENTIAL) 58 .setOverrideDeadline(runByMillis) 59 .setMinimumLatency(runFromMillis) 60 .setPersisted(true) 61 .build(); 62 final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null); 63 mTaskStoreUnderTest.add(ts); 64 Thread.sleep(IO_WAIT); 65 // Manually load tasks from xml file. 66 final JobSet jobStatusSet = new JobSet(); 67 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 68 69 assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size()); 70 final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0); 71 assertTasksEqual(task, loadedTaskStatus.getJob()); 72 assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts)); 73 assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid()); 74 compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", 75 ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime()); 76 compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", 77 ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed()); 78 79 } 80 testWritingTwoFilesToDisk()81 public void testWritingTwoFilesToDisk() throws Exception { 82 final JobInfo task1 = new Builder(8, mComponent) 83 .setRequiresDeviceIdle(true) 84 .setPeriodic(10000L) 85 .setRequiresCharging(true) 86 .setPersisted(true) 87 .build(); 88 final JobInfo task2 = new Builder(12, mComponent) 89 .setMinimumLatency(5000L) 90 .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) 91 .setOverrideDeadline(30000L) 92 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 93 .setPersisted(true) 94 .build(); 95 final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, SOME_UID, null, -1, null); 96 final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null); 97 mTaskStoreUnderTest.add(taskStatus1); 98 mTaskStoreUnderTest.add(taskStatus2); 99 Thread.sleep(IO_WAIT); 100 101 final JobSet jobStatusSet = new JobSet(); 102 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 103 assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size()); 104 Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator(); 105 JobStatus loaded1 = it.next(); 106 JobStatus loaded2 = it.next(); 107 108 // Reverse them so we know which comparison to make. 109 if (loaded1.getJobId() != 8) { 110 JobStatus tmp = loaded1; 111 loaded1 = loaded2; 112 loaded2 = tmp; 113 } 114 115 assertTasksEqual(task1, loaded1.getJob()); 116 assertTasksEqual(task2, loaded2.getJob()); 117 assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1)); 118 assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2)); 119 // Check that the loaded task has the correct runtimes. 120 compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", 121 taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime()); 122 compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", 123 taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed()); 124 compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", 125 taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime()); 126 compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", 127 taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed()); 128 129 } 130 testWritingTaskWithExtras()131 public void testWritingTaskWithExtras() throws Exception { 132 JobInfo.Builder b = new Builder(8, mComponent) 133 .setRequiresDeviceIdle(true) 134 .setPeriodic(10000L) 135 .setRequiresCharging(true) 136 .setPersisted(true); 137 138 PersistableBundle extras = new PersistableBundle(); 139 extras.putDouble("hello", 3.2); 140 extras.putString("hi", "there"); 141 extras.putInt("into", 3); 142 b.setExtras(extras); 143 final JobInfo task = b.build(); 144 JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null); 145 146 mTaskStoreUnderTest.add(taskStatus); 147 Thread.sleep(IO_WAIT); 148 149 final JobSet jobStatusSet = new JobSet(); 150 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 151 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 152 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 153 assertTasksEqual(task, loaded.getJob()); 154 } testWritingTaskWithSourcePackage()155 public void testWritingTaskWithSourcePackage() throws Exception { 156 JobInfo.Builder b = new Builder(8, mComponent) 157 .setRequiresDeviceIdle(true) 158 .setPeriodic(10000L) 159 .setRequiresCharging(true) 160 .setPersisted(true); 161 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, 162 "com.google.android.gms", 0, null); 163 164 mTaskStoreUnderTest.add(taskStatus); 165 Thread.sleep(IO_WAIT); 166 167 final JobSet jobStatusSet = new JobSet(); 168 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 169 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 170 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 171 assertEquals("Source package not equal.", loaded.getSourcePackageName(), 172 taskStatus.getSourcePackageName()); 173 assertEquals("Source user not equal.", loaded.getSourceUserId(), 174 taskStatus.getSourceUserId()); 175 } 176 testWritingTaskWithFlex()177 public void testWritingTaskWithFlex() throws Exception { 178 JobInfo.Builder b = new Builder(8, mComponent) 179 .setRequiresDeviceIdle(true) 180 .setPeriodic(5*60*60*1000, 1*60*60*1000) 181 .setRequiresCharging(true) 182 .setPersisted(true); 183 JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null); 184 185 mTaskStoreUnderTest.add(taskStatus); 186 Thread.sleep(IO_WAIT); 187 188 final JobSet jobStatusSet = new JobSet(); 189 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 190 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 191 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 192 assertEquals("Period not equal.", loaded.getJob().getIntervalMillis(), 193 taskStatus.getJob().getIntervalMillis()); 194 assertEquals("Flex not equal.", loaded.getJob().getFlexMillis(), 195 taskStatus.getJob().getFlexMillis()); 196 } 197 testMassivePeriodClampedOnRead()198 public void testMassivePeriodClampedOnRead() throws Exception { 199 final long ONE_HOUR = 60*60*1000L; // flex 200 final long TWO_HOURS = 2 * ONE_HOUR; // period 201 JobInfo.Builder b = new Builder(8, mComponent) 202 .setPeriodic(TWO_HOURS, ONE_HOUR) 203 .setPersisted(true); 204 final long invalidLateRuntimeElapsedMillis = 205 SystemClock.elapsedRealtime() + (TWO_HOURS * ONE_HOUR) + TWO_HOURS; // > period+flex 206 final long invalidEarlyRuntimeElapsedMillis = 207 invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period). 208 final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 209 0 /* sourceUserId */, "someTag", 210 invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis); 211 212 mTaskStoreUnderTest.add(js); 213 Thread.sleep(IO_WAIT); 214 215 final JobSet jobStatusSet = new JobSet(); 216 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 217 assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); 218 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 219 220 // Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll 221 // call SystemClock.elapsedRealtime after doing the disk i/o. 222 final long newNowElapsed = SystemClock.elapsedRealtime(); 223 assertTrue("Early runtime wasn't correctly clamped.", 224 loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS); 225 // Assert late runtime was clamped to be now + period + flex. 226 assertTrue("Early runtime wasn't correctly clamped.", 227 loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS + ONE_HOUR); 228 } 229 testPriorityPersisted()230 public void testPriorityPersisted() throws Exception { 231 JobInfo.Builder b = new Builder(92, mComponent) 232 .setOverrideDeadline(5000) 233 .setPriority(42) 234 .setPersisted(true); 235 final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null); 236 mTaskStoreUnderTest.add(js); 237 Thread.sleep(IO_WAIT); 238 final JobSet jobStatusSet = new JobSet(); 239 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 240 JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); 241 assertEquals("Priority not correctly persisted.", 42, loaded.getPriority()); 242 } 243 244 /** 245 * Test that non persisted job is not written to disk. 246 */ testNonPersistedTaskIsNotPersisted()247 public void testNonPersistedTaskIsNotPersisted() throws Exception { 248 JobInfo.Builder b = new Builder(42, mComponent) 249 .setOverrideDeadline(10000) 250 .setPersisted(false); 251 JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null); 252 mTaskStoreUnderTest.add(jsNonPersisted); 253 b = new Builder(43, mComponent) 254 .setOverrideDeadline(10000) 255 .setPersisted(true); 256 JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null); 257 mTaskStoreUnderTest.add(jsPersisted); 258 Thread.sleep(IO_WAIT); 259 final JobSet jobStatusSet = new JobSet(); 260 mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet); 261 assertEquals("Job count is incorrect.", 1, jobStatusSet.size()); 262 JobStatus jobStatus = jobStatusSet.getAllJobs().iterator().next(); 263 assertEquals("Wrong job persisted.", 43, jobStatus.getJobId()); 264 } 265 266 /** 267 * Helper function to throw an error if the provided task and TaskStatus objects are not equal. 268 */ assertTasksEqual(JobInfo first, JobInfo second)269 private void assertTasksEqual(JobInfo first, JobInfo second) { 270 assertEquals("Different task ids.", first.getId(), second.getId()); 271 assertEquals("Different components.", first.getService(), second.getService()); 272 assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic()); 273 assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis()); 274 assertEquals("Different inital backoff.", first.getInitialBackoffMillis(), 275 second.getInitialBackoffMillis()); 276 assertEquals("Different backoff policy.", first.getBackoffPolicy(), 277 second.getBackoffPolicy()); 278 279 assertEquals("Invalid charging constraint.", first.isRequireCharging(), 280 second.isRequireCharging()); 281 assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(), 282 second.isRequireDeviceIdle()); 283 assertEquals("Invalid unmetered constraint.", 284 first.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED, 285 second.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED); 286 assertEquals("Invalid connectivity constraint.", 287 first.getNetworkType() == JobInfo.NETWORK_TYPE_ANY, 288 second.getNetworkType() == JobInfo.NETWORK_TYPE_ANY); 289 assertEquals("Invalid deadline constraint.", 290 first.hasLateConstraint(), 291 second.hasLateConstraint()); 292 assertEquals("Invalid delay constraint.", 293 first.hasEarlyConstraint(), 294 second.hasEarlyConstraint()); 295 assertEquals("Extras don't match", 296 first.getExtras().toString(), second.getExtras().toString()); 297 } 298 299 /** 300 * When comparing timestamps before and after DB read/writes (to make sure we're saving/loading 301 * the correct values), there is some latency involved that terrorises a naive assertEquals(). 302 * We define a <code>DELTA_MILLIS</code> as a function variable here to make this comparision 303 * more reasonable. 304 */ compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2)305 private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) { 306 final long DELTA_MILLIS = 700L; // We allow up to 700ms of latency for IO read/writes. 307 assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT); 308 } 309 310 private static class StubClass {} 311 312 } 313