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