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