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     private boolean mWaitingForStop;
     56 
     57     @Override
     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
     68     public void onCreate() {
     69         super.onCreate();
     70         Log.i(TAG, "Created test service.");
     71     }
     72 
     73     @Override
     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
    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 
    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 
    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 
    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 
    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
    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 
    310         public static TestEnvironment getTestEnvironment() {
    311             if (kTestEnvironment == null) {
    312                 kTestEnvironment = new TestEnvironment();
    313             }
    314             return kTestEnvironment;
    315         }
    316 
    317         public TestWorkItem[] getExpectedWork() {
    318             return mExpectedWork;
    319         }
    320 
    321         public JobParameters getLastJobParameters() {
    322             return mExecutedJobParameters;
    323         }
    324 
    325         public int getLastPermCheckRead() {
    326             return mExecutedPermCheckRead;
    327         }
    328 
    329         public int getLastPermCheckWrite() {
    330             return mExecutedPermCheckWrite;
    331         }
    332 
    333         public ArrayList<JobWorkItem> getLastReceivedWork() {
    334             return mExecutedReceivedWork;
    335         }
    336 
    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          */
    345         public boolean awaitExecution() throws InterruptedException {
    346             return awaitExecution(DEFAULT_TIMEOUT_MILLIS);
    347         }
    348 
    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          */
    362         public boolean awaitTimeout() throws InterruptedException {
    363             return awaitTimeout(DEFAULT_TIMEOUT_MILLIS);
    364         }
    365 
    366         public boolean awaitTimeout(long timeoutMillis) throws InterruptedException {
    367             return !mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
    368         }
    369 
    370         public boolean awaitWaitingForStop() throws InterruptedException {
    371             return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    372         }
    373 
    374         public boolean awaitDoWork() throws InterruptedException {
    375             return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    376         }
    377 
    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 
    385         public boolean awaitStopped() throws InterruptedException {
    386             return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    387         }
    388 
    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 
    400         private void notifyWaitingForStop() {
    401             mWaitingForStopLatch.countDown();
    402         }
    403 
    404         private void notifyStopped() {
    405             if (mStoppedLatch != null) {
    406                 mStoppedLatch.countDown();
    407             }
    408         }
    409 
    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 
    425         public void setExpectedWaitForStop() {
    426             mWaitingForStopLatch = new CountDownLatch(1);
    427         }
    428 
    429         public void setExpectedWork(TestWorkItem[] work) {
    430             mExpectedWork = work;
    431             mDoWorkLatch = new CountDownLatch(1);
    432         }
    433 
    434         public void setExpectedStopped() {
    435             mStoppedLatch = new CountDownLatch(1);
    436         }
    437 
    438         public void readyToWork() {
    439             mDoWorkLatch.countDown();
    440         }
    441 
    442         public void setExpectedWaitForRun() {
    443             mDoJobLatch = new CountDownLatch(1);
    444         }
    445 
    446         public void readyToRun() {
    447             mDoJobLatch.countDown();
    448         }
    449 
    450         public void setContinueAfterStart() {
    451             mContinueAfterStart = true;
    452         }
    453 
    454         public boolean handleContinueAfterStart() {
    455             boolean res = mContinueAfterStart;
    456             mContinueAfterStart = false;
    457             return res;
    458         }
    459 
    460         /** Called in each testCase#setup */
    461         public void setUp() {
    462             mLatch = null;
    463             mExecutedJobParameters = null;
    464         }
    465 
    466     }
    467 }