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 }