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 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 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 processNextPendingCompletion()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 onStopJob(JobParameters params)269 public boolean onStopJob(JobParameters params) { 270 Log.i(TAG, "Received stop callback"); 271 TestEnvironment.getTestEnvironment().notifyStopped(params); 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 TestWorkItem(Intent _intent)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 TestWorkItem(Intent _intent, int _flags)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 TestWorkItem(Intent _intent, int _flags, int _deliveryCount)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 TestWorkItem(Intent _intent, JobInfo _jobInfo, TestWorkItem[] _subitems)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 TestWorkItem(Intent _intent, Uri[] _requireUrisGranted, Uri[] _requireUrisNotGranted)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 toString()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 private JobParameters mStopJobParameters; 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 getLastStartJobParameters()395 public JobParameters getLastStartJobParameters() { 396 return mExecutedJobParameters; 397 } 398 getLastStopJobParameters()399 public JobParameters getLastStopJobParameters() { 400 return mStopJobParameters; 401 } 402 getLastPermCheckRead()403 public int getLastPermCheckRead() { 404 return mExecutedPermCheckRead; 405 } 406 getLastPermCheckWrite()407 public int getLastPermCheckWrite() { 408 return mExecutedPermCheckWrite; 409 } 410 getLastReceivedWork()411 public ArrayList<JobWorkItem> getLastReceivedWork() { 412 return mExecutedReceivedWork; 413 } 414 getLastErrorMessage()415 public String getLastErrorMessage() { 416 return mExecutedErrorMessage; 417 } 418 419 /** 420 * Block the test thread, waiting on the JobScheduler to execute some previously scheduled 421 * job on this service. 422 */ awaitExecution()423 public boolean awaitExecution() throws InterruptedException { 424 return awaitExecution(DEFAULT_TIMEOUT_MILLIS); 425 } 426 awaitExecution(long timeoutMillis)427 public boolean awaitExecution(long timeoutMillis) throws InterruptedException { 428 final boolean executed = mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 429 if (getLastErrorMessage() != null) { 430 Assert.fail(getLastErrorMessage()); 431 } 432 return executed; 433 } 434 435 /** 436 * Block the test thread, expecting to timeout but still listening to ensure that no jobs 437 * land in the interim. 438 * @return True if the latch timed out waiting on an execution. 439 */ awaitTimeout()440 public boolean awaitTimeout() throws InterruptedException { 441 return awaitTimeout(DEFAULT_TIMEOUT_MILLIS); 442 } 443 awaitTimeout(long timeoutMillis)444 public boolean awaitTimeout(long timeoutMillis) throws InterruptedException { 445 return !mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 446 } 447 awaitWaitingForStop()448 public boolean awaitWaitingForStop() throws InterruptedException { 449 return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 450 } 451 awaitDoWork()452 public boolean awaitDoWork() throws InterruptedException { 453 return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 454 } 455 awaitDoJob()456 public boolean awaitDoJob() throws InterruptedException { 457 if (mDoJobLatch == null) { 458 return true; 459 } 460 return mDoJobLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 461 } 462 awaitStopped()463 public boolean awaitStopped() throws InterruptedException { 464 return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 465 } 466 notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite, ArrayList<JobWorkItem> receivedWork, String errorMsg)467 private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite, 468 ArrayList<JobWorkItem> receivedWork, String errorMsg) { 469 //Log.d(TAG, "Job executed:" + params.getJobId()); 470 mExecutedJobParameters = params; 471 mExecutedPermCheckRead = permCheckRead; 472 mExecutedPermCheckWrite = permCheckWrite; 473 mExecutedReceivedWork = receivedWork; 474 mExecutedErrorMessage = errorMsg; 475 if (mLatch != null) { 476 mLatch.countDown(); 477 } 478 } 479 notifyWaitingForStop()480 private void notifyWaitingForStop() { 481 mWaitingForStopLatch.countDown(); 482 } 483 notifyStopped(JobParameters params)484 private void notifyStopped(JobParameters params) { 485 mStopJobParameters = params; 486 if (mStoppedLatch != null) { 487 mStoppedLatch.countDown(); 488 } 489 } 490 setExpectedExecutions(int numExecutions)491 public void setExpectedExecutions(int numExecutions) { 492 // For no executions expected, set count to 1 so we can still block for the timeout. 493 if (numExecutions == 0) { 494 mLatch = new CountDownLatch(1); 495 } else { 496 mLatch = new CountDownLatch(numExecutions); 497 } 498 mWaitingForStopLatch = null; 499 mDoJobLatch = null; 500 mStoppedLatch = null; 501 mDoWorkLatch = null; 502 mExpectedWork = null; 503 mContinueAfterStart = false; 504 } 505 setExpectedWaitForStop()506 public void setExpectedWaitForStop() { 507 mWaitingForStopLatch = new CountDownLatch(1); 508 } 509 setExpectedWork(TestWorkItem[] work)510 public void setExpectedWork(TestWorkItem[] work) { 511 mExpectedWork = work; 512 mDoWorkLatch = new CountDownLatch(1); 513 } 514 setExpectedStopped()515 public void setExpectedStopped() { 516 mStoppedLatch = new CountDownLatch(1); 517 } 518 readyToWork()519 public void readyToWork() { 520 mDoWorkLatch.countDown(); 521 } 522 setExpectedWaitForRun()523 public void setExpectedWaitForRun() { 524 mDoJobLatch = new CountDownLatch(1); 525 } 526 readyToRun()527 public void readyToRun() { 528 mDoJobLatch.countDown(); 529 } 530 setContinueAfterStart()531 public void setContinueAfterStart() { 532 mContinueAfterStart = true; 533 } 534 handleContinueAfterStart()535 public boolean handleContinueAfterStart() { 536 boolean res = mContinueAfterStart; 537 mContinueAfterStart = false; 538 return res; 539 } 540 541 /** Called in each testCase#setup */ setUp()542 public void setUp() { 543 mLatch = null; 544 mExecutedJobParameters = null; 545 mStopJobParameters = null; 546 } 547 548 } 549 }