1 /*
2  * Copyright (C) 2016 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.content.Context;
25 import android.os.Handler;
26 import android.util.Log;
27 
28 import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.TimeUnit;
30 
31 /**
32  * Handles callback from the framework {@link android.app.job.JobScheduler}. The behaviour of this
33  * class is configured through the static
34  * {@link TestEnvironment}.
35  */
36 @TargetApi(21)
37 public class TriggerContentJobService extends JobService {
38     private static final String TAG = "TriggerContentJobService";
39 
40     /** Wait this long before timing out the test. */
41     private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds.
42 
43     /** How long to delay before rescheduling the job each time we repeat. */
44     private static final long REPEAT_INTERVAL = 1000L; // 1 second.
45 
46     JobInfo mRunningJobInfo;
47     JobParameters mRunningParams;
48 
49     final Handler mHandler = new Handler();
50     final Runnable mWorker = new Runnable() {
51         @Override public void run() {
52             scheduleJob(TriggerContentJobService.this, mRunningJobInfo);
53             jobFinished(mRunningParams, false);
54         }
55     };
56 
scheduleJob(Context context, JobInfo jobInfo)57     public static void scheduleJob(Context context, JobInfo jobInfo) {
58         JobScheduler js = context.getSystemService(JobScheduler.class);
59         js.schedule(jobInfo);
60     }
61 
62     @Override
onCreate()63     public void onCreate() {
64         super.onCreate();
65         Log.e(TAG, "Created test service.");
66     }
67 
68     @Override
onStartJob(JobParameters params)69     public boolean onStartJob(JobParameters params) {
70         Log.i(TAG, "Test job executing: " + params.getJobId());
71 
72         int mode = TestEnvironment.getTestEnvironment().getMode();
73         mRunningJobInfo = TestEnvironment.getTestEnvironment().getModeJobInfo();
74         TestEnvironment.getTestEnvironment().setMode(TestEnvironment.MODE_ONESHOT, null);
75         TestEnvironment.getTestEnvironment().notifyExecution(params);
76 
77         if (mode == TestEnvironment.MODE_ONE_REPEAT) {
78             mRunningParams = params;
79             mHandler.postDelayed(mWorker, REPEAT_INTERVAL);
80             return true;
81         } else {
82             return false;  // No work to do.
83         }
84     }
85 
86     @Override
onStopJob(JobParameters params)87     public boolean onStopJob(JobParameters params) {
88         return false;
89     }
90 
91     /**
92      * Configures the expected behaviour for each test. This object is shared across consecutive
93      * tests, so to clear state each test is responsible for calling
94      * {@link TestEnvironment#setUp()}.
95      */
96     public static final class TestEnvironment {
97 
98         private static TestEnvironment kTestEnvironment;
99         //public static final int INVALID_JOB_ID = -1;
100 
101         private CountDownLatch mLatch;
102         private JobParameters mExecutedJobParameters;
103         private int mMode;
104         private JobInfo mModeJobInfo;
105 
106         public static final int MODE_ONESHOT = 0;
107         public static final int MODE_ONE_REPEAT = 1;
108 
getTestEnvironment()109         public static TestEnvironment getTestEnvironment() {
110             if (kTestEnvironment == null) {
111                 kTestEnvironment = new TestEnvironment();
112             }
113             return kTestEnvironment;
114         }
115 
getLastJobParameters()116         public JobParameters getLastJobParameters() {
117             return mExecutedJobParameters;
118         }
119 
120         /**
121          * Block the test thread, waiting on the JobScheduler to execute some previously scheduled
122          * job on this service.
123          */
awaitExecution()124         public boolean awaitExecution() throws InterruptedException {
125             final boolean executed = mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
126             return executed;
127         }
128 
setMode(int mode, JobInfo jobInfo)129         public void setMode(int mode, JobInfo jobInfo) {
130             synchronized (this) {
131                 mMode = mode;
132                 mModeJobInfo = jobInfo;
133             }
134         }
135 
getMode()136         public int getMode() {
137             synchronized (this) {
138                 return mMode;
139             }
140         }
141 
getModeJobInfo()142         public JobInfo getModeJobInfo() {
143             synchronized (this) {
144                 return mModeJobInfo;
145             }
146         }
147 
148         /**
149          * Block the test thread, expecting to timeout but still listening to ensure that no jobs
150          * land in the interim.
151          * @return True if the latch timed out waiting on an execution.
152          */
awaitTimeout()153         public boolean awaitTimeout() throws InterruptedException {
154             return !mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
155         }
156 
notifyExecution(JobParameters params)157         private void notifyExecution(JobParameters params) {
158             Log.d(TAG, "Job executed:" + params.getJobId());
159             mExecutedJobParameters = params;
160             mLatch.countDown();
161         }
162 
setExpectedExecutions(int numExecutions)163         public void setExpectedExecutions(int numExecutions) {
164             // For no executions expected, set count to 1 so we can still block for the timeout.
165             if (numExecutions == 0) {
166                 mLatch = new CountDownLatch(1);
167             } else {
168                 mLatch = new CountDownLatch(numExecutions);
169             }
170         }
171 
172         /** Called in each testCase#setup */
setUp()173         public void setUp() {
174             mLatch = null;
175             mExecutedJobParameters = null;
176         }
177 
178     }
179 }