• 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.concurrent.CountDownLatch;
37 import 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)
45 public class MockJobService extends JobService {
46     private static final String TAG = "MockJobService";
47 
48     /** Wait this long before timing out the test. */
49     private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds.
50 
51     private JobParameters mParams;
52 
53     ArrayList<JobWorkItem> mReceivedWork = new ArrayList<>();
54 
55     private boolean mWaitingForStop;
56 
57     @Override
onDestroy()58     public void onDestroy() {
59         super.onDestroy();
60         Log.i(TAG, "Destroying test service");
61         if (TestEnvironment.getTestEnvironment().getExpectedWork() != null) {
62             TestEnvironment.getTestEnvironment().notifyExecution(mParams, 0, 0, mReceivedWork,
63                     null);
64         }
65     }
66 
67     @Override
onCreate()68     public void onCreate() {
69         super.onCreate();
70         Log.i(TAG, "Created test service.");
71     }
72 
73     @Override
onStartJob(JobParameters params)74     public boolean onStartJob(JobParameters params) {
75         Log.i(TAG, "Test job executing: " + params.getJobId());
76         mParams = params;
77 
78         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 
88         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());
105                 mReceivedWork.add(work);
106 
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                         }
171                     }
172 
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                     }
179                 }
180 
181                 mParams.completeWork(work);
182 
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++;
197             }
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             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             }
216             TestEnvironment.getTestEnvironment().notifyExecution(params, permCheckRead,
217                     permCheckWrite, null, null);
218             return continueAfterStart;
219         }
220     }
221 
222     @Override
onStopJob(JobParameters params)223     public boolean onStopJob(JobParameters params) {
224         Log.i(TAG, "Received stop callback");
225         TestEnvironment.getTestEnvironment().notifyStopped();
226         return mWaitingForStop;
227     }
228 
229     public static final class TestWorkItem {
230         public static final int FLAG_WAIT_FOR_STOP = 1<<0;
231 
232         public final Intent intent;
233         public final JobInfo jobInfo;
234         public final int flags;
235         public final int deliveryCount;
236         public final TestWorkItem[] subitems;
237         public final Uri[] requireUrisGranted;
238         public final Uri[] requireUrisNotGranted;
239 
TestWorkItem(Intent _intent)240         public TestWorkItem(Intent _intent) {
241             intent = _intent;
242             jobInfo = null;
243             flags = 0;
244             deliveryCount = 1;
245             subitems = null;
246             requireUrisGranted = null;
247             requireUrisNotGranted = null;
248         }
249 
TestWorkItem(Intent _intent, int _flags, int _deliveryCount)250         public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
251             intent = _intent;
252             jobInfo = null;
253             flags = _flags;
254             deliveryCount = _deliveryCount;
255             subitems = null;
256             requireUrisGranted = null;
257             requireUrisNotGranted = null;
258         }
259 
TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems)260         public TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems) {
261             intent = _intent;
262             jobInfo = _jobInfo;
263             flags = 0;
264             deliveryCount = 1;
265             subitems = _subitems;
266             requireUrisGranted = null;
267             requireUrisNotGranted = null;
268         }
269 
TestWorkItem(Intent _intent, Uri[] _requireUrisGranted, Uri[] _requireUrisNotGranted)270         public TestWorkItem(Intent _intent, Uri[] _requireUrisGranted,
271                 Uri[] _requireUrisNotGranted) {
272             intent = _intent;
273             jobInfo = null;
274             flags = 0;
275             deliveryCount = 1;
276             subitems = null;
277             requireUrisGranted = _requireUrisGranted;
278             requireUrisNotGranted = _requireUrisNotGranted;
279         }
280 
281         @Override
toString()282         public String toString() {
283             return "TestWorkItem { " + intent + " dc=" + deliveryCount + " }";
284         }
285     }
286 
287     /**
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;
295         //public static final int INVALID_JOB_ID = -1;
296 
297         private CountDownLatch mLatch;
298         private CountDownLatch mWaitingForStopLatch;
299         private CountDownLatch mDoJobLatch;
300         private CountDownLatch mStoppedLatch;
301         private CountDownLatch mDoWorkLatch;
302         private TestWorkItem[] mExpectedWork;
303         private boolean mContinueAfterStart;
304         private JobParameters mExecutedJobParameters;
305         private int mExecutedPermCheckRead;
306         private int mExecutedPermCheckWrite;
307         private ArrayList<JobWorkItem> mExecutedReceivedWork;
308         private String mExecutedErrorMessage;
309 
getTestEnvironment()310         public static TestEnvironment getTestEnvironment() {
311             if (kTestEnvironment == null) {
312                 kTestEnvironment = new TestEnvironment();
313             }
314             return kTestEnvironment;
315         }
316 
getExpectedWork()317         public TestWorkItem[] getExpectedWork() {
318             return mExpectedWork;
319         }
320 
getLastJobParameters()321         public JobParameters getLastJobParameters() {
322             return mExecutedJobParameters;
323         }
324 
getLastPermCheckRead()325         public int getLastPermCheckRead() {
326             return mExecutedPermCheckRead;
327         }
328 
getLastPermCheckWrite()329         public int getLastPermCheckWrite() {
330             return mExecutedPermCheckWrite;
331         }
332 
getLastReceivedWork()333         public ArrayList<JobWorkItem> getLastReceivedWork() {
334             return mExecutedReceivedWork;
335         }
336 
getLastErrorMessage()337         public String getLastErrorMessage() {
338             return mExecutedErrorMessage;
339         }
340 
341         /**
342          * Block the test thread, waiting on the JobScheduler to execute some previously scheduled
343          * job on this service.
344          */
awaitExecution()345         public boolean awaitExecution() throws InterruptedException {
346             return awaitExecution(DEFAULT_TIMEOUT_MILLIS);
347         }
348 
awaitExecution(long timeoutMillis)349         public boolean awaitExecution(long timeoutMillis) throws InterruptedException {
350             final boolean executed = mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
351             if (getLastErrorMessage() != null) {
352                 Assert.fail(getLastErrorMessage());
353             }
354             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          */
awaitTimeout()362         public boolean awaitTimeout() throws InterruptedException {
363             return awaitTimeout(DEFAULT_TIMEOUT_MILLIS);
364         }
365 
awaitTimeout(long timeoutMillis)366         public boolean awaitTimeout(long timeoutMillis) throws InterruptedException {
367             return !mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
368         }
369 
awaitWaitingForStop()370         public boolean awaitWaitingForStop() throws InterruptedException {
371             return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
372         }
373 
awaitDoWork()374         public boolean awaitDoWork() throws InterruptedException {
375             return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
376         }
377 
awaitDoJob()378         public boolean awaitDoJob() throws InterruptedException {
379             if (mDoJobLatch == null) {
380                 return true;
381             }
382             return mDoJobLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
383         }
384 
awaitStopped()385         public boolean awaitStopped() throws InterruptedException {
386             return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
387         }
388 
notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite, ArrayList<JobWorkItem> receivedWork, String errorMsg)389         private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
390                 ArrayList<JobWorkItem> receivedWork, String errorMsg) {
391             //Log.d(TAG, "Job executed:" + params.getJobId());
392             mExecutedJobParameters = params;
393             mExecutedPermCheckRead = permCheckRead;
394             mExecutedPermCheckWrite = permCheckWrite;
395             mExecutedReceivedWork = receivedWork;
396             mExecutedErrorMessage = errorMsg;
397             mLatch.countDown();
398         }
399 
notifyWaitingForStop()400         private void notifyWaitingForStop() {
401             mWaitingForStopLatch.countDown();
402         }
403 
notifyStopped()404         private void notifyStopped() {
405             if (mStoppedLatch != null) {
406                 mStoppedLatch.countDown();
407             }
408         }
409 
setExpectedExecutions(int numExecutions)410         public void setExpectedExecutions(int numExecutions) {
411             // For no executions expected, set count to 1 so we can still block for the timeout.
412             if (numExecutions == 0) {
413                 mLatch = new CountDownLatch(1);
414             } else {
415                 mLatch = new CountDownLatch(numExecutions);
416             }
417             mWaitingForStopLatch = null;
418             mDoJobLatch = null;
419             mStoppedLatch = null;
420             mDoWorkLatch = null;
421             mExpectedWork = null;
422             mContinueAfterStart = false;
423         }
424 
setExpectedWaitForStop()425         public void setExpectedWaitForStop() {
426             mWaitingForStopLatch = new CountDownLatch(1);
427         }
428 
setExpectedWork(TestWorkItem[] work)429         public void setExpectedWork(TestWorkItem[] work) {
430             mExpectedWork = work;
431             mDoWorkLatch = new CountDownLatch(1);
432         }
433 
setExpectedStopped()434         public void setExpectedStopped() {
435             mStoppedLatch = new CountDownLatch(1);
436         }
437 
readyToWork()438         public void readyToWork() {
439             mDoWorkLatch.countDown();
440         }
441 
setExpectedWaitForRun()442         public void setExpectedWaitForRun() {
443             mDoJobLatch = new CountDownLatch(1);
444         }
445 
readyToRun()446         public void readyToRun() {
447             mDoJobLatch.countDown();
448         }
449 
setContinueAfterStart()450         public void setContinueAfterStart() {
451             mContinueAfterStart = true;
452         }
453 
handleContinueAfterStart()454         public boolean handleContinueAfterStart() {
455             boolean res = mContinueAfterStart;
456             mContinueAfterStart = false;
457             return res;
458         }
459 
460         /** Called in each testCase#setup */
setUp()461         public void setUp() {
462             mLatch = null;
463             mExecutedJobParameters = null;
464         }
465 
466     }
467 }