blob: 4ade3b58ba76508800bd4b6874f50134e98a989e [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 {
91 if (TestEnvironment.getTestEnvironment().awaitDoWork()) {
92 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 {
203 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
204 permCheckWrite, null, null);
205 return false; // No work to do.
206 }
Matthew Williams547b8162014-10-15 10:18:11 -0700207 }
208
209 @Override
210 public boolean onStopJob(JobParameters params) {
Dianne Hackborne6866832017-04-26 14:08:32 -0700211 Log.i(TAG, "Received stop callback");
212 return mWaitingForStop;
Matthew Williams547b8162014-10-15 10:18:11 -0700213 }
214
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700215 public static final class TestWorkItem {
Dianne Hackborne6866832017-04-26 14:08:32 -0700216 public static final int FLAG_WAIT_FOR_STOP = 1<<0;
217
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700218 public final Intent intent;
219 public final JobInfo jobInfo;
Dianne Hackborne6866832017-04-26 14:08:32 -0700220 public final int flags;
221 public final int deliveryCount;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700222 public final TestWorkItem[] subitems;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700223 public final Uri[] requireUrisGranted;
224 public final Uri[] requireUrisNotGranted;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700225
226 public TestWorkItem(Intent _intent) {
227 intent = _intent;
228 jobInfo = null;
Dianne Hackborne6866832017-04-26 14:08:32 -0700229 flags = 0;
230 deliveryCount = 1;
231 subitems = null;
232 requireUrisGranted = null;
233 requireUrisNotGranted = null;
234 }
235
236 public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
237 intent = _intent;
238 jobInfo = null;
239 flags = _flags;
240 deliveryCount = _deliveryCount;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700241 subitems = null;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700242 requireUrisGranted = null;
243 requireUrisNotGranted = null;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700244 }
245
246 public TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems) {
247 intent = _intent;
248 jobInfo = _jobInfo;
Dianne Hackborne6866832017-04-26 14:08:32 -0700249 flags = 0;
250 deliveryCount = 1;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700251 subitems = _subitems;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700252 requireUrisGranted = null;
253 requireUrisNotGranted = null;
254 }
255
256 public TestWorkItem(Intent _intent, Uri[] _requireUrisGranted,
257 Uri[] _requireUrisNotGranted) {
258 intent = _intent;
259 jobInfo = null;
Dianne Hackborne6866832017-04-26 14:08:32 -0700260 flags = 0;
261 deliveryCount = 1;
Dianne Hackborn9bfd0bf2017-04-13 18:07:53 -0700262 subitems = null;
263 requireUrisGranted = _requireUrisGranted;
264 requireUrisNotGranted = _requireUrisNotGranted;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700265 }
Dianne Hackborne6866832017-04-26 14:08:32 -0700266
267 @Override
268 public String toString() {
269 return "TestWorkItem { " + intent + " dc=" + deliveryCount + " }";
270 }
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700271 }
272
Matthew Williams547b8162014-10-15 10:18:11 -0700273 /**
274 * Configures the expected behaviour for each test. This object is shared across consecutive
275 * tests, so to clear state each test is responsible for calling
276 * {@link TestEnvironment#setUp()}.
277 */
278 public static final class TestEnvironment {
279
280 private static TestEnvironment kTestEnvironment;
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700281 //public static final int INVALID_JOB_ID = -1;
Matthew Williams547b8162014-10-15 10:18:11 -0700282
283 private CountDownLatch mLatch;
Dianne Hackborne6866832017-04-26 14:08:32 -0700284 private CountDownLatch mWaitingForStopLatch;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700285 private CountDownLatch mDoWorkLatch;
286 private TestWorkItem[] mExpectedWork;
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700287 private JobParameters mExecutedJobParameters;
Dianne Hackborn6a389872017-03-30 14:06:33 -0700288 private int mExecutedPermCheckRead;
289 private int mExecutedPermCheckWrite;
Dianne Hackborne6866832017-04-26 14:08:32 -0700290 private ArrayList<JobWorkItem> mExecutedReceivedWork;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700291 private String mExecutedErrorMessage;
Matthew Williams547b8162014-10-15 10:18:11 -0700292
293 public static TestEnvironment getTestEnvironment() {
294 if (kTestEnvironment == null) {
295 kTestEnvironment = new TestEnvironment();
296 }
297 return kTestEnvironment;
298 }
299
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700300 public TestWorkItem[] getExpectedWork() {
301 return mExpectedWork;
302 }
303
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700304 public JobParameters getLastJobParameters() {
305 return mExecutedJobParameters;
306 }
307
Dianne Hackborn6a389872017-03-30 14:06:33 -0700308 public int getLastPermCheckRead() {
309 return mExecutedPermCheckRead;
310 }
311
312 public int getLastPermCheckWrite() {
313 return mExecutedPermCheckWrite;
314 }
315
Dianne Hackborne6866832017-04-26 14:08:32 -0700316 public ArrayList<JobWorkItem> getLastReceivedWork() {
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700317 return mExecutedReceivedWork;
318 }
319
320 public String getLastErrorMessage() {
321 return mExecutedErrorMessage;
322 }
323
Matthew Williams547b8162014-10-15 10:18:11 -0700324 /**
325 * Block the test thread, waiting on the JobScheduler to execute some previously scheduled
326 * job on this service.
327 */
328 public boolean awaitExecution() throws InterruptedException {
329 final boolean executed = mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700330 if (getLastErrorMessage() != null) {
331 Assert.fail(getLastErrorMessage());
332 }
Matthew Williams547b8162014-10-15 10:18:11 -0700333 return executed;
334 }
335
336 /**
337 * Block the test thread, expecting to timeout but still listening to ensure that no jobs
338 * land in the interim.
339 * @return True if the latch timed out waiting on an execution.
340 */
341 public boolean awaitTimeout() throws InterruptedException {
342 return !mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
343 }
344
Dianne Hackborne6866832017-04-26 14:08:32 -0700345 public boolean awaitWaitingForStop() throws InterruptedException {
346 return !mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
347 }
348
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700349 public boolean awaitDoWork() throws InterruptedException {
350 return !mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
351 }
352
353 private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
Dianne Hackborne6866832017-04-26 14:08:32 -0700354 ArrayList<JobWorkItem> receivedWork, String errorMsg) {
Dianne Hackborn6a389872017-03-30 14:06:33 -0700355 //Log.d(TAG, "Job executed:" + params.getJobId());
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700356 mExecutedJobParameters = params;
Dianne Hackborn6a389872017-03-30 14:06:33 -0700357 mExecutedPermCheckRead = permCheckRead;
358 mExecutedPermCheckWrite = permCheckWrite;
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700359 mExecutedReceivedWork = receivedWork;
360 mExecutedErrorMessage = errorMsg;
Matthew Williams547b8162014-10-15 10:18:11 -0700361 mLatch.countDown();
362 }
363
Dianne Hackborne6866832017-04-26 14:08:32 -0700364 private void notifyWaitingForStop() {
365 mWaitingForStopLatch.countDown();
366 }
367
Matthew Williams547b8162014-10-15 10:18:11 -0700368 public void setExpectedExecutions(int numExecutions) {
369 // For no executions expected, set count to 1 so we can still block for the timeout.
370 if (numExecutions == 0) {
371 mLatch = new CountDownLatch(1);
372 } else {
373 mLatch = new CountDownLatch(numExecutions);
374 }
375 }
376
Dianne Hackborne6866832017-04-26 14:08:32 -0700377 public void setExpectedWaitForStop() {
378 mWaitingForStopLatch = new CountDownLatch(1);
379 }
380
Dianne Hackborn555b9c72017-04-10 15:18:05 -0700381 public void setExpectedWork(TestWorkItem[] work) {
382 mExpectedWork = work;
383 mDoWorkLatch = new CountDownLatch(1);
384 }
385
386 public void readyToWork() {
387 mDoWorkLatch.countDown();
388 }
389
Matthew Williams547b8162014-10-15 10:18:11 -0700390 /** Called in each testCase#setup */
391 public void setUp() {
392 mLatch = null;
Matthew Williamsbe8620e2015-05-14 17:49:50 -0700393 mExecutedJobParameters = null;
Matthew Williams547b8162014-10-15 10:18:11 -0700394 }
395
396 }
397}