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 mWorkerReschedule = new Runnable() {
51         @Override public void run() {
52             scheduleJob(TriggerContentJobService.this, mRunningJobInfo);
53             jobFinished(mRunningParams, false);
54         }
55     };
56     final Runnable mWorkerFinishTrue = new Runnable() {
57         @Override public void run() {
58             jobFinished(mRunningParams, true);
59         }
60     };
61 
scheduleJob(Context context, JobInfo jobInfo)62     public static void scheduleJob(Context context, JobInfo jobInfo) {
63         JobScheduler js = context.getSystemService(JobScheduler.class);
64         js.schedule(jobInfo);
65     }
66 
67     @Override
onCreate()68     public void onCreate() {
69         super.onCreate();
70         Log.e(TAG, "Created test service.");
71     }
72 
73     @Override
onStartJob(JobParameters params)74     public boolean onStartJob(JobParameters params) {
75         Log.i(TAG, "Test job executing: " + params.getJobId());
76 
77         int mode = TestEnvironment.getTestEnvironment().getMode();
78         mRunningJobInfo = TestEnvironment.getTestEnvironment().getModeJobInfo();
79         TestEnvironment.getTestEnvironment().setMode(TestEnvironment.MODE_ONESHOT, null);
80         TestEnvironment.getTestEnvironment().notifyExecution(params);
81 
82         if (mode == TestEnvironment.MODE_ONE_REPEAT_RESCHEDULE) {
83             mRunningParams = params;
84             mHandler.postDelayed(mWorkerReschedule, REPEAT_INTERVAL);
85             return true;
86         } else if (mode == TestEnvironment.MODE_ONE_REPEAT_FINISH_TRUE) {
87             mRunningParams = params;
88             mHandler.postDelayed(mWorkerFinishTrue, REPEAT_INTERVAL);
89             return true;
90         } else {
91             return false;  // No work to do.
92         }
93     }
94 
95     @Override
onStopJob(JobParameters params)96     public boolean onStopJob(JobParameters params) {
97         return false;
98     }
99 
100     /**
101      * Configures the expected behaviour for each test. This object is shared across consecutive
102      * tests, so to clear state each test is responsible for calling
103      * {@link TestEnvironment#setUp()}.
104      */
105     public static final class TestEnvironment {
106 
107         private static TestEnvironment kTestEnvironment;
108         //public static final int INVALID_JOB_ID = -1;
109 
110         private CountDownLatch mLatch;
111         private JobParameters mExecutedJobParameters;
112         private int mMode;
113         private JobInfo mModeJobInfo;
114 
115         public static final int MODE_ONESHOT = 0;
116         public static final int MODE_ONE_REPEAT_RESCHEDULE = 1;
117         public static final int MODE_ONE_REPEAT_FINISH_TRUE = 2;
118 
getTestEnvironment()119         public static TestEnvironment getTestEnvironment() {
120             if (kTestEnvironment == null) {
121                 kTestEnvironment = new TestEnvironment();
122             }
123             return kTestEnvironment;
124         }
125 
getLastJobParameters()126         public JobParameters getLastJobParameters() {
127             return mExecutedJobParameters;
128         }
129 
130         /**
131          * Block the test thread, waiting on the JobScheduler to execute some previously scheduled
132          * job on this service.
133          */
awaitExecution()134         public boolean awaitExecution() throws InterruptedException {
135             final boolean executed = mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
136             return executed;
137         }
138 
setMode(int mode, JobInfo jobInfo)139         public void setMode(int mode, JobInfo jobInfo) {
140             synchronized (this) {
141                 mMode = mode;
142                 mModeJobInfo = jobInfo;
143             }
144         }
145 
getMode()146         public int getMode() {
147             synchronized (this) {
148                 return mMode;
149             }
150         }
151 
getModeJobInfo()152         public JobInfo getModeJobInfo() {
153             synchronized (this) {
154                 return mModeJobInfo;
155             }
156         }
157 
158         /**
159          * Block the test thread, expecting to timeout but still listening to ensure that no jobs
160          * land in the interim.
161          * @return True if the latch timed out waiting on an execution.
162          */
awaitTimeout()163         public boolean awaitTimeout() throws InterruptedException {
164             return !mLatch.await(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
165         }
166 
notifyExecution(JobParameters params)167         private void notifyExecution(JobParameters params) {
168             Log.d(TAG, "Job executed:" + params.getJobId());
169             mExecutedJobParameters = params;
170             mLatch.countDown();
171         }
172 
setExpectedExecutions(int numExecutions)173         public void setExpectedExecutions(int numExecutions) {
174             // For no executions expected, set count to 1 so we can still block for the timeout.
175             if (numExecutions == 0) {
176                 mLatch = new CountDownLatch(1);
177             } else {
178                 mLatch = new CountDownLatch(numExecutions);
179             }
180         }
181 
182         /** Called in each testCase#setup */
setUp()183         public void setUp() {
184             mLatch = null;
185             mExecutedJobParameters = null;
186         }
187 
188     }
189 }