• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.jobscheduler;
18 
19 import android.annotation.TargetApi;
20 import android.app.job.JobInfo;
21 import android.app.job.JobParameters;
22 import android.app.job.JobScheduler;
23 import android.app.job.JobService;
24 import android.app.job.JobWorkItem;
25 import android.content.ClipData;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.net.Uri;
30 import android.os.Process;
31 import android.util.Log;
32 
33 import junit.framework.Assert;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.CountDownLatch;
38 import java.util.concurrent.TimeUnit;
39 
40 /**
41  * Handles callback from the framework {@link android.app.job.JobScheduler}. The behaviour of this
42  * class is configured through the static
43  * {@link TestEnvironment}.
44  */
45 @TargetApi(21)
46 public class MockJobService extends JobService {
47     private static final String TAG = "MockJobService";
48 
49     /** Wait this long before timing out the test. */
50     private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds.
51 
52     private JobParameters mParams;
53 
54     ArrayList<JobWorkItem> mReceivedWork = new ArrayList<>();
55 
56     ArrayList<JobWorkItem> mPendingCompletions = new ArrayList<>();
57 
58     private boolean mWaitingForStop;
59 
60     @Override
onDestroy()61     public void onDestroy() {
62         super.onDestroy();
63         Log.i(TAG, "Destroying test service");
64         if (TestEnvironment.getTestEnvironment().getExpectedWork() != null) {
65             TestEnvironment.getTestEnvironment().notifyExecution(mParams, 0, 0, mReceivedWork,
66                     null);
67         }
68     }
69 
70     @Override
onCreate()71     public void onCreate() {
72         super.onCreate();
73         Log.i(TAG, "Created test service.");
74     }
75 
76     @Override
onStartJob(JobParameters params)77     public boolean onStartJob(JobParameters params) {
78         Log.i(TAG, "Test job executing: " + params.getJobId());
79         mParams = params;
80         TestEnvironment.getTestEnvironment().addEvent(
81                 new TestEnvironment.Event(
82                         TestEnvironment.Event.EVENT_START_JOB, params.getJobId()));
83 
84         int permCheckRead = PackageManager.PERMISSION_DENIED;
85         int permCheckWrite = PackageManager.PERMISSION_DENIED;
86         ClipData clip = params.getClipData();
87         if (clip != null) {
88             permCheckRead = checkUriPermission(clip.getItemAt(0).getUri(), Process.myPid(),
89                     Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION);
90             permCheckWrite = checkUriPermission(clip.getItemAt(0).getUri(), Process.myPid(),
91                     Process.myUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
92         }
93 
94         TestWorkItem[] expectedWork = TestEnvironment.getTestEnvironment().getExpectedWork();
95         if (expectedWork != null) {
96             try {
97                 if (!TestEnvironment.getTestEnvironment().awaitDoWork()) {
98                     TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
99                             permCheckWrite, null, "Spent too long waiting to start executing work");
100                     return false;
101                 }
102             } catch (InterruptedException e) {
103                 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
104                         permCheckWrite, null, "Failed waiting for work: " + e);
105                 return false;
106             }
107             JobWorkItem work;
108             int index = 0;
109             while ((work = params.dequeueWork()) != null) {
110                 Log.i(TAG, "Received work #" + index + ": " + work.getIntent());
111                 mReceivedWork.add(work);
112 
113                 int flags = 0;
114 
115                 if (index < expectedWork.length) {
116                     TestWorkItem expected = expectedWork[index];
117                     int grantFlags = work.getIntent().getFlags();
118                     if (expected.requireUrisGranted != null) {
119                         for (int ui = 0; ui < expected.requireUrisGranted.length; ui++) {
120                             if ((grantFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
121                                 if (checkUriPermission(expected.requireUrisGranted[ui],
122                                         Process.myPid(), Process.myUid(),
123                                         Intent.FLAG_GRANT_READ_URI_PERMISSION)
124                                         != PackageManager.PERMISSION_GRANTED) {
125                                     TestEnvironment.getTestEnvironment().notifyExecution(params,
126                                             permCheckRead, permCheckWrite, null,
127                                             "Expected read permission but not granted: "
128                                                     + expected.requireUrisGranted[ui]
129                                                     + " @ #" + index);
130                                     return false;
131                                 }
132                             }
133                             if ((grantFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
134                                 if (checkUriPermission(expected.requireUrisGranted[ui],
135                                         Process.myPid(), Process.myUid(),
136                                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
137                                         != PackageManager.PERMISSION_GRANTED) {
138                                     TestEnvironment.getTestEnvironment().notifyExecution(params,
139                                             permCheckRead, permCheckWrite, null,
140                                             "Expected write permission but not granted: "
141                                                     + expected.requireUrisGranted[ui]
142                                                     + " @ #" + index);
143                                     return false;
144                                 }
145                             }
146                         }
147                     }
148                     if (expected.requireUrisNotGranted != null) {
149                         // XXX note no delay here, current impl will have fully revoked the
150                         // permission by the time we return from completing the last work.
151                         for (int ui = 0; ui < expected.requireUrisNotGranted.length; ui++) {
152                             if ((grantFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
153                                 if (checkUriPermission(expected.requireUrisNotGranted[ui],
154                                         Process.myPid(), Process.myUid(),
155                                         Intent.FLAG_GRANT_READ_URI_PERMISSION)
156                                         != PackageManager.PERMISSION_DENIED) {
157                                     TestEnvironment.getTestEnvironment().notifyExecution(params,
158                                             permCheckRead, permCheckWrite, null,
159                                             "Not expected read permission but granted: "
160                                                     + expected.requireUrisNotGranted[ui]
161                                                     + " @ #" + index);
162                                     return false;
163                                 }
164                             }
165                             if ((grantFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
166                                 if (checkUriPermission(expected.requireUrisNotGranted[ui],
167                                         Process.myPid(), Process.myUid(),
168                                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
169                                         != PackageManager.PERMISSION_DENIED) {
170                                     TestEnvironment.getTestEnvironment().notifyExecution(params,
171                                             permCheckRead, permCheckWrite, null,
172                                             "Not expected write permission but granted: "
173                                                     + expected.requireUrisNotGranted[ui]
174                                                     + " @ #" + index);
175                                     return false;
176                                 }
177                             }
178                         }
179                     }
180 
181                     flags = expected.flags;
182 
183                     if ((flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
184                         Log.i(TAG, "Now waiting to stop");
185                         mWaitingForStop = true;
186                         TestEnvironment.getTestEnvironment().notifyWaitingForStop();
187                         return true;
188                     }
189 
190                     if ((flags & TestWorkItem.FLAG_COMPLETE_NEXT) != 0) {
191                         if (!processNextPendingCompletion()) {
192                             TestEnvironment.getTestEnvironment().notifyExecution(params,
193                                     0, 0, null,
194                                     "Expected to complete next pending work but there was none: "
195                                             + " @ #" + index);
196                             return false;
197                         }
198                     }
199                 }
200 
201                 if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) != 0) {
202                     mPendingCompletions.add(work);
203                 } else if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) != 0) {
204                     mPendingCompletions.add(0, work);
205                 } else {
206                     mParams.completeWork(work);
207                 }
208 
209                 if (index < expectedWork.length) {
210                     TestWorkItem expected = expectedWork[index];
211                     if (expected.subitems != null) {
212                         final TestWorkItem[] sub = expected.subitems;
213                         final JobInfo ji = expected.jobInfo;
214                         final JobScheduler js = (JobScheduler) getSystemService(
215                                 Context.JOB_SCHEDULER_SERVICE);
216                         for (int subi = 0; subi < sub.length; subi++) {
217                             js.enqueue(ji, new JobWorkItem(sub[subi].intent));
218                         }
219                     }
220                 }
221 
222                 index++;
223             }
224 
225             if (processNextPendingCompletion()) {
226                 // We had some pending completions, clean them all out...
227                 while (processNextPendingCompletion()) {
228                 }
229                 // ...and we need to do a final dequeue to complete the job, which should not
230                 // return any remaining work.
231                 if ((work = params.dequeueWork()) != null) {
232                     TestEnvironment.getTestEnvironment().notifyExecution(params,
233                             0, 0, null,
234                             "Expected no remaining work after dequeue pending, but got: " + work);
235                 }
236             }
237 
238             Log.i(TAG, "Done with all work at #" + index);
239             // We don't notifyExecution here because we want to make sure the job properly
240             // stops itself.
241             return true;
242         } else {
243             boolean continueAfterStart
244                     = TestEnvironment.getTestEnvironment().handleContinueAfterStart();
245             try {
246                 if (!TestEnvironment.getTestEnvironment().awaitDoJob()) {
247                     TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
248                             permCheckWrite, null, "Spent too long waiting to start job");
249                     return false;
250                 }
251             } catch (InterruptedException e) {
252                 TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
253                         permCheckWrite, null, "Failed waiting to start job: " + e);
254                 return false;
255             }
256             TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
257                     permCheckWrite, null, null);
258             return continueAfterStart;
259         }
260     }
261 
processNextPendingCompletion()262     boolean processNextPendingCompletion() {
263         if (mPendingCompletions.size() <= 0) {
264             return false;
265         }
266 
267         JobWorkItem next = mPendingCompletions.remove(0);
268         mParams.completeWork(next);
269         return true;
270     }
271 
272     @Override
onStopJob(JobParameters params)273     public boolean onStopJob(JobParameters params) {
274         Log.i(TAG, "Received stop callback");
275         TestEnvironment.getTestEnvironment().notifyStopped(params);
276         return mWaitingForStop;
277     }
278 
279     public static final class TestWorkItem {
280         /**
281          * Stop processing work for now, waiting for the service to be stopped.
282          */
283         public static final int FLAG_WAIT_FOR_STOP = 1<<0;
284         /**
285          * Don't complete this work now, instead push it on the back of the stack of
286          * pending completions.
287          */
288         public static final int FLAG_DELAY_COMPLETE_PUSH_BACK = 1<<1;
289         /**
290          * Don't complete this work now, instead insert to the top of the stack of
291          * pending completions.
292          */
293         public static final int FLAG_DELAY_COMPLETE_PUSH_TOP = 1<<2;
294         /**
295          * Complete next pending completion on the stack before completing this one.
296          */
297         public static final int FLAG_COMPLETE_NEXT = 1<<3;
298 
299         public final Intent intent;
300         public final JobInfo jobInfo;
301         public final int flags;
302         public final int deliveryCount;
303         public final TestWorkItem[] subitems;
304         public final Uri[] requireUrisGranted;
305         public final Uri[] requireUrisNotGranted;
306 
TestWorkItem(Intent _intent)307         public TestWorkItem(Intent _intent) {
308             intent = _intent;
309             jobInfo = null;
310             flags = 0;
311             deliveryCount = 1;
312             subitems = null;
313             requireUrisGranted = null;
314             requireUrisNotGranted = null;
315         }
316 
TestWorkItem(Intent _intent, int _flags)317         public TestWorkItem(Intent _intent, int _flags) {
318             intent = _intent;
319             jobInfo = null;
320             flags = _flags;
321             deliveryCount = 1;
322             subitems = null;
323             requireUrisGranted = null;
324             requireUrisNotGranted = null;
325         }
326 
TestWorkItem(Intent _intent, int _flags, int _deliveryCount)327         public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
328             intent = _intent;
329             jobInfo = null;
330             flags = _flags;
331             deliveryCount = _deliveryCount;
332             subitems = null;
333             requireUrisGranted = null;
334             requireUrisNotGranted = null;
335         }
336 
TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems)337         public TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems) {
338             intent = _intent;
339             jobInfo = _jobInfo;
340             flags = 0;
341             deliveryCount = 1;
342             subitems = _subitems;
343             requireUrisGranted = null;
344             requireUrisNotGranted = null;
345         }
346 
TestWorkItem(Intent _intent, Uri[] _requireUrisGranted, Uri[] _requireUrisNotGranted)347         public TestWorkItem(Intent _intent, Uri[] _requireUrisGranted,
348                 Uri[] _requireUrisNotGranted) {
349             intent = _intent;
350             jobInfo = null;
351             flags = 0;
352             deliveryCount = 1;
353             subitems = null;
354             requireUrisGranted = _requireUrisGranted;
355             requireUrisNotGranted = _requireUrisNotGranted;
356         }
357 
358         @Override
toString()359         public String toString() {
360             return "TestWorkItem { " + intent + " dc=" + deliveryCount + " }";
361         }
362     }
363 
364     /**
365      * Configures the expected behaviour for each test. This object is shared across consecutive
366      * tests, so to clear state each test is responsible for calling
367      * {@link TestEnvironment#setUp()}.
368      */
369     public static final class TestEnvironment {
370 
371         private static TestEnvironment kTestEnvironment;
372         //public static final int INVALID_JOB_ID = -1;
373 
374         private CountDownLatch mLatch;
375         private CountDownLatch mWaitingForStopLatch;
376         private CountDownLatch mDoJobLatch;
377         private CountDownLatch mStoppedLatch;
378         private CountDownLatch mDoWorkLatch;
379         private TestWorkItem[] mExpectedWork;
380         private boolean mContinueAfterStart;
381         private JobParameters mExecutedJobParameters;
382         private int mExecutedPermCheckRead;
383         private int mExecutedPermCheckWrite;
384         private ArrayList<JobWorkItem> mExecutedReceivedWork;
385         private String mExecutedErrorMessage;
386         private JobParameters mStopJobParameters;
387         private List<Event> mExecutedEvents = new ArrayList<>();
388 
getTestEnvironment()389         public static TestEnvironment getTestEnvironment() {
390             if (kTestEnvironment == null) {
391                 kTestEnvironment = new TestEnvironment();
392             }
393             return kTestEnvironment;
394         }
395 
getExpectedWork()396         public TestWorkItem[] getExpectedWork() {
397             return mExpectedWork;
398         }
399 
getLastStartJobParameters()400         public JobParameters getLastStartJobParameters() {
401             return mExecutedJobParameters;
402         }
403 
getLastStopJobParameters()404         public JobParameters getLastStopJobParameters() {
405             return mStopJobParameters;
406         }
407 
getLastPermCheckRead()408         public int getLastPermCheckRead() {
409             return mExecutedPermCheckRead;
410         }
411 
getLastPermCheckWrite()412         public int getLastPermCheckWrite() {
413             return mExecutedPermCheckWrite;
414         }
415 
getLastReceivedWork()416         public ArrayList<JobWorkItem> getLastReceivedWork() {
417             return mExecutedReceivedWork;
418         }
419 
getLastErrorMessage()420         public String getLastErrorMessage() {
421             return mExecutedErrorMessage;
422         }
423 
424         /**
425          * Block the test thread, waiting on the JobScheduler to execute some previously scheduled
426          * job on this service.
427          */
awaitExecution()428         public boolean awaitExecution() throws InterruptedException {
429             return awaitExecution(DEFAULT_TIMEOUT_MILLIS);
430         }
431 
awaitExecution(long timeoutMillis)432         public boolean awaitExecution(long timeoutMillis) throws InterruptedException {
433             final boolean executed = mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
434             if (getLastErrorMessage() != null) {
435                 Assert.fail(getLastErrorMessage());
436             }
437             return executed;
438         }
439 
440         /**
441          * Block the test thread, expecting to timeout but still listening to ensure that no jobs
442          * land in the interim.
443          * @return True if the latch timed out waiting on an execution.
444          */
awaitTimeout()445         public boolean awaitTimeout() throws InterruptedException {
446             return awaitTimeout(DEFAULT_TIMEOUT_MILLIS);
447         }
448 
awaitTimeout(long timeoutMillis)449         public boolean awaitTimeout(long timeoutMillis) throws InterruptedException {
450             return !mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
451         }
452 
awaitWaitingForStop()453         public boolean awaitWaitingForStop() throws InterruptedException {
454             return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
455         }
456 
awaitDoWork()457         public boolean awaitDoWork() throws InterruptedException {
458             return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
459         }
460 
awaitDoJob()461         public boolean awaitDoJob() throws InterruptedException {
462             if (mDoJobLatch == null) {
463                 return true;
464             }
465             return mDoJobLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
466         }
467 
awaitStopped()468         public boolean awaitStopped() throws InterruptedException {
469             return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
470         }
471 
notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite, ArrayList<JobWorkItem> receivedWork, String errorMsg)472         private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
473                 ArrayList<JobWorkItem> receivedWork, String errorMsg) {
474             mExecutedJobParameters = params;
475             mExecutedPermCheckRead = permCheckRead;
476             mExecutedPermCheckWrite = permCheckWrite;
477             mExecutedReceivedWork = receivedWork;
478             mExecutedErrorMessage = errorMsg;
479             if (mLatch != null) {
480                 mLatch.countDown();
481             }
482         }
483 
notifyWaitingForStop()484         private void notifyWaitingForStop() {
485             mWaitingForStopLatch.countDown();
486         }
487 
notifyStopped(JobParameters params)488         private void notifyStopped(JobParameters params) {
489             mStopJobParameters = params;
490             if (mStoppedLatch != null) {
491                 mStoppedLatch.countDown();
492             }
493         }
494 
setExpectedExecutions(int numExecutions)495         public void setExpectedExecutions(int numExecutions) {
496             // For no executions expected, set count to 1 so we can still block for the timeout.
497             if (numExecutions == 0) {
498                 mLatch = new CountDownLatch(1);
499             } else {
500                 mLatch = new CountDownLatch(numExecutions);
501             }
502             mWaitingForStopLatch = null;
503             mDoJobLatch = null;
504             mStoppedLatch = null;
505             mDoWorkLatch = null;
506             mExpectedWork = null;
507             mContinueAfterStart = false;
508             mExecutedEvents.clear();
509         }
510 
setExpectedWaitForStop()511         public void setExpectedWaitForStop() {
512             mWaitingForStopLatch = new CountDownLatch(1);
513         }
514 
setExpectedWork(TestWorkItem[] work)515         public void setExpectedWork(TestWorkItem[] work) {
516             mExpectedWork = work;
517             mDoWorkLatch = new CountDownLatch(1);
518         }
519 
setExpectedStopped()520         public void setExpectedStopped() {
521             mStoppedLatch = new CountDownLatch(1);
522         }
523 
readyToWork()524         public void readyToWork() {
525             mDoWorkLatch.countDown();
526         }
527 
setExpectedWaitForRun()528         public void setExpectedWaitForRun() {
529             mDoJobLatch = new CountDownLatch(1);
530         }
531 
readyToRun()532         public void readyToRun() {
533             mDoJobLatch.countDown();
534         }
535 
setContinueAfterStart()536         public void setContinueAfterStart() {
537             mContinueAfterStart = true;
538         }
539 
handleContinueAfterStart()540         public boolean handleContinueAfterStart() {
541             boolean res = mContinueAfterStart;
542             mContinueAfterStart = false;
543             return res;
544         }
545 
546         /** Called in each testCase#setup */
setUp()547         public void setUp() {
548             mLatch = null;
549             mExecutedJobParameters = null;
550             mStopJobParameters = null;
551         }
552 
addEvent(Event event)553         void addEvent(Event event) {
554             mExecutedEvents.add(event);
555         }
556 
getExecutedEvents()557         public List<Event> getExecutedEvents() {
558             return mExecutedEvents;
559         }
560 
561         public static class Event {
562             public static final int EVENT_START_JOB = 0;
563 
564             public int event;
565             public int jobId;
566 
Event(int event, int jobId)567             public Event(int event, int jobId) {
568                 this.event = event;
569                 this.jobId = jobId;
570             }
571 
572             @Override
equals(Object other)573             public boolean equals(Object other) {
574                 if (this == other) {
575                     return true;
576                 }
577                 if (other instanceof Event) {
578                     Event otherEvent = (Event) other;
579                     return otherEvent.event == event && otherEvent.jobId == jobId;
580                 }
581                 return false;
582             }
583 
584             @Override
hashCode()585             public int hashCode() {
586                 return event + 31 * jobId;
587             }
588 
589             @Override
toString()590             public String toString() {
591                 return "Event{" + event + ", " + jobId + "}";
592             }
593         }
594     }
595 }
596