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