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