blob: 0ca3ecc04a5f0ca3999b0623f2c39f1a4e67e28e [file] [log] [blame]
Matthew Williams547b8162014-10-15 10:18:11 -07001/*
2 * Copyright (C) 2014 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
17package android.jobscheduler;
18
19import android.annotation.TargetApi;
Dianne Hackborn555b9c72017-04-10 15:18:05 -070020import android.app.job.JobInfo;
Matthew Williams547b8162014-10-15 10:18:11 -070021import android.app.job.JobParameters;
Dianne Hackborn555b9c72017-04-10 15:18:05 -070022import android.app.job.JobScheduler;
Matthew Williams547b8162014-10-15 10:18:11 -070023import android.app.job.JobService;
Dianne Hackborn555b9c72017-04-10 15:18:05 -070024import android.app.job.JobWorkItem;
Dianne Hackborn6a389872017-03-30 14:06:33 -070025import android.content.ClipData;
Dianne Hackborn555b9c72017-04-10 15:18:05 -070026import android.content.Context;
Dianne Hackborn6a389872017-03-30 14:06:33 -070027import android.content.Intent;
28import android.content.pm.PackageManager;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -070029import android.net.Uri;
Dianne Hackborn6a389872017-03-30 14:06:33 -070030import android.os.Process;
Matthew Williams547b8162014-10-15 10:18:11 -070031import android.util.Log;
32
Dianne Hackborn555b9c72017-04-10 15:18:05 -070033import junit.framework.Assert;
34
35import java.util.ArrayList;
Matthew Williams547b8162014-10-15 10:18:11 -070036import java.util.concurrent.CountDownLatch;
37import java.util.concurrent.TimeUnit;
38
39/**
40 * Handles callback from the framework {@link android.app.job.JobScheduler}. The behaviour of this
41 * class is configured through the static
42 * {@link TestEnvironment}.
43 */
44@TargetApi(21)
45public class MockJobService extends JobService {
46 private static final String TAG = "MockJobService";
47
48 /** Wait this long before timing out the test. */
Christopher Tate459fffd2015-01-16 15:03:46 -080049 private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds.
Matthew Williams547b8162014-10-15 10:18:11 -070050
Dianne Hackborn555b9c72017-04-10 15:18:05 -070051 private JobParameters mParams;
52
Dianne Hackborne6866832017-04-26 14:08:32 -070053 ArrayList<JobWorkItem> mReceivedWork = new ArrayList<>();
54
55 private boolean mWaitingForStop;
Dianne Hackborn555b9c72017-04-10 15:18:05 -070056
57 @Override
58 public void onDestroy() {
59 super.onDestroy();
Dianne Hackborne6866832017-04-26 14:08:32 -070060 Log.i(TAG, "Destroying test service");
Dianne Hackborn555b9c72017-04-10 15:18:05 -070061 if (TestEnvironment.getTestEnvironment().getExpectedWork() != null) {
62 TestEnvironment.getTestEnvironment().notifyExecution(mParams, 0, 0, mReceivedWork,
63 null);
64 }
65 }
66
Matthew Williams547b8162014-10-15 10:18:11 -070067 @Override
68 public void onCreate() {
69 super.onCreate();
Dianne Hackborn555b9c72017-04-10 15:18:05 -070070 Log.i(TAG, "Created test service.");
Matthew Williams547b8162014-10-15 10:18:11 -070071 }
72
73 @Override
74 public boolean onStartJob(JobParameters params) {
75 Log.i(TAG, "Test job executing: " + params.getJobId());
Dianne Hackborn555b9c72017-04-10 15:18:05 -070076 mParams = params;
Matthew Williams547b8162014-10-15 10:18:11 -070077
Dianne Hackborn6a389872017-03-30 14:06:33 -070078 int permCheckRead = PackageManager.PERMISSION_DENIED;
79 int permCheckWrite = PackageManager.PERMISSION_DENIED;
80 ClipData clip = params.getClipData();
81 if (clip != null) {
82 permCheckRead = checkUriPermission(clip.getItemAt(0).getUri(), Process.myPid(),
83 Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION);
84 permCheckWrite = checkUriPermission(clip.getItemAt(0).getUri(), Process.myPid(),
85 Process.myUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
86 }
87
Dianne Hackborn555b9c72017-04-10 15:18:05 -070088 TestWorkItem[] expectedWork = TestEnvironment.getTestEnvironment().getExpectedWork();
89 if (expectedWork != null) {
90 try {
Dianne Hackborn3338e9a52017-05-16 13:18:34 -070091 if (!TestEnvironment.getTestEnvironment().awaitDoWork()) {
Dianne Hackborn555b9c72017-04-10 15:18:05 -070092 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
93 permCheckWrite, null, "Spent too long waiting to start executing work");
94 return false;
95 }
96 } catch (InterruptedException e) {
97 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
98 permCheckWrite, null, "Failed waiting for work: " + e);
99 return false;
100 }
101 JobWorkItem work;
102 int index = 0;
103 while ((work = params.dequeueWork()) != null) {
104 Log.i(TAG, "Received work #" + index + ": " + work.getIntent());
Dianne Hackborne6866832017-04-26 14:08:32 -0700105 mReceivedWork.add(work);
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700106
107 if (index < expectedWork.length) {
108 TestWorkItem expected = expectedWork[index];
109 int grantFlags = work.getIntent().getFlags();
110 if (expected.requireUrisGranted != null) {
111 for (int ui = 0; ui < expected.requireUrisGranted.length; ui++) {
112 if ((grantFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
113 if (checkUriPermission(expected.requireUrisGranted[ui],
114 Process.myPid(), Process.myUid(),
115 Intent.FLAG_GRANT_READ_URI_PERMISSION)
116 != PackageManager.PERMISSION_GRANTED) {
117 TestEnvironment.getTestEnvironment().notifyExecution(params,
118 permCheckRead, permCheckWrite, null,
119 "Expected read permission but not granted: "
120 + expected.requireUrisGranted[ui]
121 + " @ #" + index);
122 return false;
123 }
124 }
125 if ((grantFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
126 if (checkUriPermission(expected.requireUrisGranted[ui],
127 Process.myPid(), Process.myUid(),
128 Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
129 != PackageManager.PERMISSION_GRANTED) {
130 TestEnvironment.getTestEnvironment().notifyExecution(params,
131 permCheckRead, permCheckWrite, null,
132 "Expected write permission but not granted: "
133 + expected.requireUrisGranted[ui]
134 + " @ #" + index);
135 return false;
136 }
137 }
138 }
139 }
140 if (expected.requireUrisNotGranted != null) {
141 // XXX note no delay here, current impl will have fully revoked the
142 // permission by the time we return from completing the last work.
143 for (int ui = 0; ui < expected.requireUrisNotGranted.length; ui++) {
144 if ((grantFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
145 if (checkUriPermission(expected.requireUrisNotGranted[ui],
146 Process.myPid(), Process.myUid(),
147 Intent.FLAG_GRANT_READ_URI_PERMISSION)
148 != PackageManager.PERMISSION_DENIED) {
149 TestEnvironment.getTestEnvironment().notifyExecution(params,
150 permCheckRead, permCheckWrite, null,
151 "Not expected read permission but granted: "
152 + expected.requireUrisNotGranted[ui]
153 + " @ #" + index);
154 return false;
155 }
156 }
157 if ((grantFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
158 if (checkUriPermission(expected.requireUrisNotGranted[ui],
159 Process.myPid(), Process.myUid(),
160 Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
161 != PackageManager.PERMISSION_DENIED) {
162 TestEnvironment.getTestEnvironment().notifyExecution(params,
163 permCheckRead, permCheckWrite, null,
164 "Not expected write permission but granted: "
165 + expected.requireUrisNotGranted[ui]
166 + " @ #" + index);
167 return false;
168 }
169 }
170 }
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700171 }
Dianne Hackborne6866832017-04-26 14:08:32 -0700172
173 if ((expected.flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
174 Log.i(TAG, "Now waiting to stop");
175 mWaitingForStop = true;
176 TestEnvironment.getTestEnvironment().notifyWaitingForStop();
177 return true;
178 }
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700179 }
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700180
Dianne Hackborne6866832017-04-26 14:08:32 -0700181 mParams.completeWork(work);
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700182
183 if (index < expectedWork.length) {
184 TestWorkItem expected = expectedWork[index];
185 if (expected.subitems != null) {
186 final TestWorkItem[] sub = expected.subitems;
187 final JobInfo ji = expected.jobInfo;
188 final JobScheduler js = (JobScheduler) getSystemService(
189 Context.JOB_SCHEDULER_SERVICE);
190 for (int subi = 0; subi < sub.length; subi++) {
191 js.enqueue(ji, new JobWorkItem(sub[subi].intent));
192 }
193 }
194 }
195
196 index++;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700197 }
198 Log.i(TAG, "Done with all work at #" + index);
199 // We don't notifyExecution here because we want to make sure the job properly
200 // stops itself.
201 return true;
202 } else {
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700203 boolean continueAfterStart
204 = TestEnvironment.getTestEnvironment().handleContinueAfterStart();
205 try {
206 if (!TestEnvironment.getTestEnvironment().awaitDoJob()) {
207 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
208 permCheckWrite, null, "Spent too long waiting to start job");
209 return false;
210 }
211 } catch (InterruptedException e) {
212 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
213 permCheckWrite, null, "Failed waiting to start job: " + e);
214 return false;
215 }
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700216 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
217 permCheckWrite, null, null);
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700218 return continueAfterStart;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700219 }
Matthew Williams547b8162014-10-15 10:18:11 -0700220 }
221
222 @Override
223 public boolean onStopJob(JobParameters params) {
Dianne Hackborne6866832017-04-26 14:08:32 -0700224 Log.i(TAG, "Received stop callback");
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700225 TestEnvironment.getTestEnvironment().notifyStopped();
Dianne Hackborne6866832017-04-26 14:08:32 -0700226 return mWaitingForStop;
Matthew Williams547b8162014-10-15 10:18:11 -0700227 }
228
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700229 public static final class TestWorkItem {
Dianne Hackborne6866832017-04-26 14:08:32 -0700230 public static final int FLAG_WAIT_FOR_STOP = 1<<0;
231
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700232 public final Intent intent;
233 public final JobInfo jobInfo;
Dianne Hackborne6866832017-04-26 14:08:32 -0700234 public final int flags;
235 public final int deliveryCount;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700236 public final TestWorkItem[] subitems;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700237 public final Uri[] requireUrisGranted;
238 public final Uri[] requireUrisNotGranted;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700239
240 public TestWorkItem(Intent _intent) {
241 intent = _intent;
242 jobInfo = null;
Dianne Hackborne6866832017-04-26 14:08:32 -0700243 flags = 0;
244 deliveryCount = 1;
245 subitems = null;
246 requireUrisGranted = null;
247 requireUrisNotGranted = null;
248 }
249
250 public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
251 intent = _intent;
252 jobInfo = null;
253 flags = _flags;
254 deliveryCount = _deliveryCount;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700255 subitems = null;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700256 requireUrisGranted = null;
257 requireUrisNotGranted = null;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700258 }
259
260 public TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems) {
261 intent = _intent;
262 jobInfo = _jobInfo;
Dianne Hackborne6866832017-04-26 14:08:32 -0700263 flags = 0;
264 deliveryCount = 1;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700265 subitems = _subitems;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700266 requireUrisGranted = null;
267 requireUrisNotGranted = null;
268 }
269
270 public TestWorkItem(Intent _intent, Uri[] _requireUrisGranted,
271 Uri[] _requireUrisNotGranted) {
272 intent = _intent;
273 jobInfo = null;
Dianne Hackborne6866832017-04-26 14:08:32 -0700274 flags = 0;
275 deliveryCount = 1;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700276 subitems = null;
277 requireUrisGranted = _requireUrisGranted;
278 requireUrisNotGranted = _requireUrisNotGranted;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700279 }
Dianne Hackborne6866832017-04-26 14:08:32 -0700280
281 @Override
282 public String toString() {
283 return "TestWorkItem { " + intent + " dc=" + deliveryCount + " }";
284 }
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700285 }
286
Matthew Williams547b8162014-10-15 10:18:11 -0700287 /**
288 * Configures the expected behaviour for each test. This object is shared across consecutive
289 * tests, so to clear state each test is responsible for calling
290 * {@link TestEnvironment#setUp()}.
291 */
292 public static final class TestEnvironment {
293
294 private static TestEnvironment kTestEnvironment;
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700295 //public static final int INVALID_JOB_ID = -1;
Matthew Williams547b8162014-10-15 10:18:11 -0700296
297 private CountDownLatch mLatch;
Dianne Hackborne6866832017-04-26 14:08:32 -0700298 private CountDownLatch mWaitingForStopLatch;
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700299 private CountDownLatch mDoJobLatch;
300 private CountDownLatch mStoppedLatch;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700301 private CountDownLatch mDoWorkLatch;
302 private TestWorkItem[] mExpectedWork;
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700303 private boolean mContinueAfterStart;
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700304 private JobParameters mExecutedJobParameters;
Dianne Hackborn6a389872017-03-30 14:06:33 -0700305 private int mExecutedPermCheckRead;
306 private int mExecutedPermCheckWrite;
Dianne Hackborne6866832017-04-26 14:08:32 -0700307 private ArrayList<JobWorkItem> mExecutedReceivedWork;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700308 private String mExecutedErrorMessage;
Matthew Williams547b8162014-10-15 10:18:11 -0700309
310 public static TestEnvironment getTestEnvironment() {
311 if (kTestEnvironment == null) {
312 kTestEnvironment = new TestEnvironment();
313 }
314 return kTestEnvironment;
315 }
316
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700317 public TestWorkItem[] getExpectedWork() {
318 return mExpectedWork;
319 }
320
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700321 public JobParameters getLastJobParameters() {
322 return mExecutedJobParameters;
323 }
324
Dianne Hackborn6a389872017-03-30 14:06:33 -0700325 public int getLastPermCheckRead() {
326 return mExecutedPermCheckRead;
327 }
328
329 public int getLastPermCheckWrite() {
330 return mExecutedPermCheckWrite;
331 }
332
Dianne Hackborne6866832017-04-26 14:08:32 -0700333 public ArrayList<JobWorkItem> getLastReceivedWork() {
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700334 return mExecutedReceivedWork;
335 }
336
337 public String getLastErrorMessage() {
338 return mExecutedErrorMessage;
339 }
340
Matthew Williams547b8162014-10-15 10:18:11 -0700341 /**
342 * Block the test thread, waiting on the JobScheduler to execute some previously scheduled
343 * job on this service.
344 */
345 public boolean awaitExecution() throws InterruptedException {
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700346 return awaitExecution(DEFAULT_TIMEOUT_MILLIS);
347 }
348
349 public boolean awaitExecution(long timeoutMillis) throws InterruptedException {
350 final boolean executed = mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700351 if (getLastErrorMessage() != null) {
352 Assert.fail(getLastErrorMessage());
353 }
Matthew Williams547b8162014-10-15 10:18:11 -0700354 return executed;
355 }
356
357 /**
358 * Block the test thread, expecting to timeout but still listening to ensure that no jobs
359 * land in the interim.
360 * @return True if the latch timed out waiting on an execution.
361 */
362 public boolean awaitTimeout() throws InterruptedException {
363 return !mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
364 }
365
Dianne Hackborne6866832017-04-26 14:08:32 -0700366 public boolean awaitWaitingForStop() throws InterruptedException {
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700367 return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
Dianne Hackborne6866832017-04-26 14:08:32 -0700368 }
369
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700370 public boolean awaitDoWork() throws InterruptedException {
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700371 return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
372 }
373
374 public boolean awaitDoJob() throws InterruptedException {
375 if (mDoJobLatch == null) {
376 return true;
377 }
378 return mDoJobLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
379 }
380
381 public boolean awaitStopped() throws InterruptedException {
382 return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700383 }
384
385 private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
Dianne Hackborne6866832017-04-26 14:08:32 -0700386 ArrayList<JobWorkItem> receivedWork, String errorMsg) {
Dianne Hackborn6a389872017-03-30 14:06:33 -0700387 //Log.d(TAG, "Job executed:" + params.getJobId());
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700388 mExecutedJobParameters = params;
Dianne Hackborn6a389872017-03-30 14:06:33 -0700389 mExecutedPermCheckRead = permCheckRead;
390 mExecutedPermCheckWrite = permCheckWrite;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700391 mExecutedReceivedWork = receivedWork;
392 mExecutedErrorMessage = errorMsg;
Matthew Williams547b8162014-10-15 10:18:11 -0700393 mLatch.countDown();
394 }
395
Dianne Hackborne6866832017-04-26 14:08:32 -0700396 private void notifyWaitingForStop() {
397 mWaitingForStopLatch.countDown();
398 }
399
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700400 private void notifyStopped() {
401 if (mStoppedLatch != null) {
402 mStoppedLatch.countDown();
403 }
404 }
405
Matthew Williams547b8162014-10-15 10:18:11 -0700406 public void setExpectedExecutions(int numExecutions) {
407 // For no executions expected, set count to 1 so we can still block for the timeout.
408 if (numExecutions == 0) {
409 mLatch = new CountDownLatch(1);
410 } else {
411 mLatch = new CountDownLatch(numExecutions);
412 }
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700413 mWaitingForStopLatch = null;
414 mDoJobLatch = null;
415 mStoppedLatch = null;
416 mDoWorkLatch = null;
417 mExpectedWork = null;
418 mContinueAfterStart = false;
Matthew Williams547b8162014-10-15 10:18:11 -0700419 }
420
Dianne Hackborne6866832017-04-26 14:08:32 -0700421 public void setExpectedWaitForStop() {
422 mWaitingForStopLatch = new CountDownLatch(1);
423 }
424
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700425 public void setExpectedWork(TestWorkItem[] work) {
426 mExpectedWork = work;
427 mDoWorkLatch = new CountDownLatch(1);
428 }
429
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700430 public void setExpectedStopped() {
431 mStoppedLatch = new CountDownLatch(1);
432 }
433
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700434 public void readyToWork() {
435 mDoWorkLatch.countDown();
436 }
437
Dianne Hackborn3338e9a52017-05-16 13:18:34 -0700438 public void setExpectedWaitForRun() {
439 mDoJobLatch = new CountDownLatch(1);
440 }
441
442 public void readyToRun() {
443 mDoJobLatch.countDown();
444 }
445
446 public void setContinueAfterStart() {
447 mContinueAfterStart = true;
448 }
449
450 public boolean handleContinueAfterStart() {
451 boolean res = mContinueAfterStart;
452 mContinueAfterStart = false;
453 return res;
454 }
455
Matthew Williams547b8162014-10-15 10:18:11 -0700456 /** Called in each testCase#setup */
457 public void setUp() {
458 mLatch = null;
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700459 mExecutedJobParameters = null;
Matthew Williams547b8162014-10-15 10:18:11 -0700460 }
461
462 }
463}