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(); 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 getTestEnvironment()383 public static TestEnvironment getTestEnvironment() { 384 if (kTestEnvironment == null) { 385 kTestEnvironment = new TestEnvironment(); 386 } 387 return kTestEnvironment; 388 } 389 getExpectedWork()390 public TestWorkItem[] getExpectedWork() { 391 return mExpectedWork; 392 } 393 getLastJobParameters()394 public JobParameters getLastJobParameters() { 395 return mExecutedJobParameters; 396 } 397 getLastPermCheckRead()398 public int getLastPermCheckRead() { 399 return mExecutedPermCheckRead; 400 } 401 getLastPermCheckWrite()402 public int getLastPermCheckWrite() { 403 return mExecutedPermCheckWrite; 404 } 405 getLastReceivedWork()406 public ArrayList<JobWorkItem> getLastReceivedWork() { 407 return mExecutedReceivedWork; 408 } 409 getLastErrorMessage()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 */ awaitExecution()418 public boolean awaitExecution() throws InterruptedException { 419 return awaitExecution(DEFAULT_TIMEOUT_MILLIS); 420 } 421 awaitExecution(long timeoutMillis)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 */ awaitTimeout()435 public boolean awaitTimeout() throws InterruptedException { 436 return awaitTimeout(DEFAULT_TIMEOUT_MILLIS); 437 } 438 awaitTimeout(long timeoutMillis)439 public boolean awaitTimeout(long timeoutMillis) throws InterruptedException { 440 return !mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 441 } 442 awaitWaitingForStop()443 public boolean awaitWaitingForStop() throws InterruptedException { 444 return mWaitingForStopLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 445 } 446 awaitDoWork()447 public boolean awaitDoWork() throws InterruptedException { 448 return mDoWorkLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 449 } 450 awaitDoJob()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 awaitStopped()458 public boolean awaitStopped() throws InterruptedException { 459 return mStoppedLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 460 } 461 notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite, ArrayList<JobWorkItem> receivedWork, String errorMsg)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 notifyWaitingForStop()473 private void notifyWaitingForStop() { 474 mWaitingForStopLatch.countDown(); 475 } 476 notifyStopped()477 private void notifyStopped() { 478 if (mStoppedLatch != null) { 479 mStoppedLatch.countDown(); 480 } 481 } 482 setExpectedExecutions(int numExecutions)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 setExpectedWaitForStop()498 public void setExpectedWaitForStop() { 499 mWaitingForStopLatch = new CountDownLatch(1); 500 } 501 setExpectedWork(TestWorkItem[] work)502 public void setExpectedWork(TestWorkItem[] work) { 503 mExpectedWork = work; 504 mDoWorkLatch = new CountDownLatch(1); 505 } 506 setExpectedStopped()507 public void setExpectedStopped() { 508 mStoppedLatch = new CountDownLatch(1); 509 } 510 readyToWork()511 public void readyToWork() { 512 mDoWorkLatch.countDown(); 513 } 514 setExpectedWaitForRun()515 public void setExpectedWaitForRun() { 516 mDoJobLatch = new CountDownLatch(1); 517 } 518 readyToRun()519 public void readyToRun() { 520 mDoJobLatch.countDown(); 521 } 522 setContinueAfterStart()523 public void setContinueAfterStart() { 524 mContinueAfterStart = true; 525 } 526 handleContinueAfterStart()527 public boolean handleContinueAfterStart() { 528 boolean res = mContinueAfterStart; 529 mContinueAfterStart = false; 530 return res; 531 } 532 533 /** Called in each testCase#setup */ setUp()534 public void setUp() { 535 mLatch = null; 536 mExecutedJobParameters = null; 537 } 538 539 } 540 }