• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package android.jobscheduler.cts;
17 
18 import static android.app.ActivityManager.getCapabilitiesSummary;
19 import static android.app.ActivityManager.procStateToString;
20 import static android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver.ACTION_JOB_SCHEDULE_RESULT;
21 import static android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver.EXTRA_REQUEST_JOB_UID_STATE;
22 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STARTED;
23 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
24 import static android.jobscheduler.cts.jobtestapp.TestJobService.INVALID_ADJ;
25 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_CAPABILITIES_KEY;
26 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_OOM_SCORE_ADJ_KEY;
27 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
28 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PROC_STATE_KEY;
29 import static android.server.wm.WindowManagerState.STATE_RESUMED;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.fail;
33 
34 import android.app.ActivityManager;
35 import android.app.AppOpsManager;
36 import android.app.job.JobParameters;
37 import android.app.job.JobScheduler;
38 import android.content.BroadcastReceiver;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.jobscheduler.cts.jobtestapp.TestActivity;
44 import android.jobscheduler.cts.jobtestapp.TestFgsService;
45 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
46 import android.os.SystemClock;
47 import android.os.UserHandle;
48 import android.server.wm.WindowManagerStateHelper;
49 import android.util.Log;
50 import android.util.SparseArray;
51 
52 import com.android.compatibility.common.util.AppOpsUtils;
53 import com.android.compatibility.common.util.AppStandbyUtils;
54 import com.android.compatibility.common.util.CallbackAsserter;
55 import com.android.compatibility.common.util.SystemUtil;
56 
57 import java.util.Map;
58 
59 /**
60  * Common functions to interact with the test app.
61  */
62 class TestAppInterface implements AutoCloseable {
63     private static final String TAG = TestAppInterface.class.getSimpleName();
64 
65     static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp";
66     private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity";
67     private static final String TEST_APP_FGS = TEST_APP_PACKAGE + ".TestFgsService";
68     static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver";
69 
70     private final Context mContext;
71     private final int mJobId;
72 
73     /* accesses must be synchronized on itself */
74     private final SparseArray<TestJobState> mTestJobStates = new SparseArray();
75 
TestAppInterface(Context ctx, int jobId)76     TestAppInterface(Context ctx, int jobId) {
77         mContext = ctx;
78         mJobId = jobId;
79 
80         final IntentFilter intentFilter = new IntentFilter();
81         intentFilter.addAction(ACTION_JOB_STARTED);
82         intentFilter.addAction(ACTION_JOB_STOPPED);
83         intentFilter.addAction(ACTION_JOB_SCHEDULE_RESULT);
84         mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
85         SystemUtil.runShellCommand(
86                 "am compat enable --no-kill ALLOW_TEST_API_ACCESS " + TEST_APP_PACKAGE);
87         if (AppStandbyUtils.isAppStandbyEnabled()) {
88             // Disable the bucket elevation so that we put the app in lower buckets.
89             SystemUtil.runShellCommand(
90                     "am compat enable --no-kill SCHEDULE_EXACT_ALARM_DOES_NOT_ELEVATE_BUCKET "
91                             + TEST_APP_PACKAGE);
92             // Force the test app out of the never bucket.
93             SystemUtil.runShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " rare");
94         }
95     }
96 
cleanup()97     void cleanup() throws Exception {
98         final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS);
99         cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
100         cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
101         mContext.sendBroadcast(cancelJobsIntent);
102         closeActivity();
103         stopFgs();
104         mContext.unregisterReceiver(mReceiver);
105         AppOpsUtils.reset(TEST_APP_PACKAGE);
106         SystemUtil.runShellCommand("am compat reset-all " + TEST_APP_PACKAGE);
107         mTestJobStates.clear();
108         forceStopApp(); // Clean up as much internal/temporary system state as possible
109     }
110 
111     @Override
close()112     public void close() throws Exception {
113         cleanup();
114     }
115 
scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob)116     void scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob)
117             throws Exception {
118         scheduleJob(allowWhileIdle, requiredNetworkType, asExpeditedJob, false);
119     }
120 
scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob, boolean asUserInitiatedJob)121     void scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob,
122             boolean asUserInitiatedJob) throws Exception {
123         scheduleJob(
124                 Map.of(
125                         TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle,
126                         TestJobSchedulerReceiver.EXTRA_AS_EXPEDITED, asExpeditedJob,
127                         TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, asUserInitiatedJob
128                 ),
129                 Map.of(
130                         TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, requiredNetworkType
131                 ));
132     }
133 
generateScheduleJobIntent(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)134     private Intent generateScheduleJobIntent(Map<String, Boolean> booleanExtras,
135             Map<String, Integer> intExtras) {
136         final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
137         scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
138         if (!intExtras.containsKey(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY)) {
139             scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mJobId);
140         }
141         booleanExtras.forEach(scheduleJobIntent::putExtra);
142         intExtras.forEach(scheduleJobIntent::putExtra);
143         scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
144         return scheduleJobIntent;
145     }
146 
scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)147     void scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)
148             throws Exception {
149         final Intent scheduleJobIntent = generateScheduleJobIntent(booleanExtras, intExtras);
150 
151         final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast(
152                 new IntentFilter(TestJobSchedulerReceiver.ACTION_JOB_SCHEDULE_RESULT));
153         mContext.sendBroadcast(scheduleJobIntent);
154         resultBroadcastAsserter.assertCalled("Didn't get schedule job result broadcast",
155                 15 /* 15 seconds */);
156     }
157 
postUiInitiatingNotification(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)158     void postUiInitiatingNotification(Map<String, Boolean> booleanExtras,
159             Map<String, Integer> intExtras) throws Exception {
160         final Intent intent = generateScheduleJobIntent(booleanExtras, intExtras);
161         intent.setAction(TestJobSchedulerReceiver.ACTION_POST_UI_INITIATING_NOTIFICATION);
162 
163         final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast(
164                 new IntentFilter(TestJobSchedulerReceiver.ACTION_NOTIFICATION_POSTED));
165         mContext.sendBroadcast(intent);
166         resultBroadcastAsserter.assertCalled("Didn't get notification posted broadcast",
167                 15 /* 15 seconds */);
168     }
169 
postFgsStartingAlarm()170     void postFgsStartingAlarm() throws Exception {
171         AppOpsUtils.setOpMode(TEST_APP_PACKAGE,
172                 AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, AppOpsManager.MODE_ALLOWED);
173         final Intent intent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_FGS_START_ALARM);
174         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
175         intent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
176 
177         final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast(
178                 new IntentFilter(TestJobSchedulerReceiver.ACTION_ALARM_SCHEDULED));
179         mContext.sendBroadcast(intent);
180         resultBroadcastAsserter.assertCalled("Didn't get alarm scheduled broadcast",
181                 15 /* 15 seconds */);
182     }
183 
184     /** Asks (not forces) JobScheduler to run the job if constraints are met. */
runSatisfiedJob()185     void runSatisfiedJob() throws Exception {
186         SystemUtil.runShellCommand("cmd jobscheduler run -s"
187                 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mJobId);
188     }
189 
190     /** Forces JobScheduler to run the job */
forceRunJob()191     void forceRunJob() throws Exception {
192         SystemUtil.runShellCommand("cmd jobscheduler run -f"
193                 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mJobId);
194     }
195 
stopJob(int stopReason, int internalStopReason)196     void stopJob(int stopReason, int internalStopReason) throws Exception {
197         SystemUtil.runShellCommand("cmd jobscheduler stop"
198                 + " -u " + UserHandle.myUserId()
199                 + " -s " + stopReason + " -i " + internalStopReason
200                 + " " + TEST_APP_PACKAGE + " " + mJobId);
201     }
202 
forceStopApp()203     void forceStopApp() {
204         SystemUtil.runShellCommand("am force-stop"
205                 + " --user " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE);
206     }
207 
setTestPackageRestricted(boolean restricted)208     void setTestPackageRestricted(boolean restricted) throws Exception {
209         AppOpsUtils.setOpMode(TEST_APP_PACKAGE, "RUN_ANY_IN_BACKGROUND",
210                 restricted ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED);
211     }
212 
cancelJob()213     void cancelJob() throws Exception {
214         SystemUtil.runShellCommand("cmd jobscheduler cancel"
215                 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mJobId);
216     }
217 
startAndKeepTestActivity()218     void startAndKeepTestActivity() {
219         startAndKeepTestActivity(false);
220     }
221 
startAndKeepTestActivity(boolean waitForResume)222     void startAndKeepTestActivity(boolean waitForResume) {
223         final Intent testActivity = new Intent();
224         testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
225         ComponentName testComponentName = new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY);
226         testActivity.setComponent(testComponentName);
227         mContext.startActivity(testActivity);
228         if (waitForResume) {
229             new WindowManagerStateHelper().waitForActivityState(testComponentName, STATE_RESUMED);
230         }
231     }
232 
closeActivity()233     void closeActivity() {
234         closeActivity(false);
235     }
236 
closeActivity(boolean waitForClose)237     void closeActivity(boolean waitForClose) {
238         mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
239         if (waitForClose) {
240             ComponentName testComponentName =
241                     new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY);
242             new WindowManagerStateHelper().waitForActivityRemoved(testComponentName);
243         }
244     }
245 
startFgs()246     void startFgs() throws Exception {
247         final Intent intent = new Intent(TestJobSchedulerReceiver.ACTION_START_FGS);
248         intent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
249 
250         final CallbackAsserter resultBroadcastAsserter =
251                 CallbackAsserter.forBroadcast(new IntentFilter(TestFgsService.ACTION_FGS_STARTED));
252         mContext.sendBroadcast(intent);
253         resultBroadcastAsserter.assertCalled("Didn't get FGS started broadcast",
254                 15 /* 15 seconds */);
255     }
256 
stopFgs()257     void stopFgs() {
258         final Intent testFgs = new Intent(TestFgsService.ACTION_STOP_FOREGROUND);
259         mContext.sendBroadcast(testFgs);
260     }
261 
262     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
263         @Override
264         public void onReceive(Context context, Intent intent) {
265             Log.d(TAG, "Received action " + intent.getAction());
266             switch (intent.getAction()) {
267                 case ACTION_JOB_STARTED:
268                 case ACTION_JOB_STOPPED:
269                     final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
270                     Log.d(TAG, "JobId: " + params.getJobId());
271                     synchronized (mTestJobStates) {
272                         TestJobState jobState = mTestJobStates.get(params.getJobId());
273                         if (jobState == null) {
274                             jobState = new TestJobState();
275                             mTestJobStates.put(params.getJobId(), jobState);
276                         } else {
277                             jobState.reset();
278                         }
279                         jobState.running = ACTION_JOB_STARTED.equals(intent.getAction());
280                         jobState.params = params;
281                         // With these broadcasts, the job is/was running, and therefore scheduling
282                         // was successful.
283                         jobState.scheduleResult = JobScheduler.RESULT_SUCCESS;
284                         if (intent.getBooleanExtra(EXTRA_REQUEST_JOB_UID_STATE, false)) {
285                             jobState.procState = intent.getIntExtra(JOB_PROC_STATE_KEY,
286                                     ActivityManager.PROCESS_STATE_NONEXISTENT);
287                             jobState.capabilities = intent.getIntExtra(JOB_CAPABILITIES_KEY,
288                                     ActivityManager.PROCESS_CAPABILITY_NONE);
289                             jobState.oomScoreAdj = intent.getIntExtra(JOB_OOM_SCORE_ADJ_KEY,
290                                     INVALID_ADJ);
291                         }
292                     }
293                     break;
294                 case ACTION_JOB_SCHEDULE_RESULT:
295                     synchronized (mTestJobStates) {
296                         final int jobId = intent.getIntExtra(
297                                 TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, 0);
298                         TestJobState jobState = mTestJobStates.get(jobId);
299                         if (jobState == null) {
300                             jobState = new TestJobState();
301                             mTestJobStates.put(jobId, jobState);
302                         } else {
303                             jobState.reset();
304                         }
305                         jobState.running = false;
306                         jobState.params = null;
307                         jobState.scheduleResult = intent.getIntExtra(
308                                 TestJobSchedulerReceiver.EXTRA_SCHEDULE_RESULT, -1);
309                     }
310                     break;
311             }
312         }
313     };
314 
awaitJobStart(long maxWait)315     boolean awaitJobStart(long maxWait) throws Exception {
316         return awaitJobStart(mJobId, maxWait);
317     }
318 
awaitJobStart(int jobId, long maxWait)319     boolean awaitJobStart(int jobId, long maxWait) throws Exception {
320         return waitUntilTrue(maxWait, () -> {
321             synchronized (mTestJobStates) {
322                 TestJobState jobState = mTestJobStates.get(jobId);
323                 return jobState != null && jobState.running;
324             }
325         });
326     }
327 
328     boolean awaitJobStop(long maxWait) throws Exception {
329         return waitUntilTrue(maxWait, () -> {
330             synchronized (mTestJobStates) {
331                 TestJobState jobState = mTestJobStates.get(mJobId);
332                 return jobState != null && !jobState.running;
333             }
334         });
335     }
336 
337     void assertJobUidState(int procState, int capabilities, int oomScoreAdj) {
338         synchronized (mTestJobStates) {
339             TestJobState jobState = mTestJobStates.get(mJobId);
340             if (jobState == null) {
341                 fail("Job not started");
342             }
343             assertEquals("procState expected=" + procStateToString(procState)
344                             + ",actual=" + procStateToString(jobState.procState),
345                     procState, jobState.procState);
346             assertEquals("capabilities expected=" + getCapabilitiesSummary(capabilities)
347                             + ",actual=" + getCapabilitiesSummary(jobState.capabilities),
348                     capabilities, jobState.capabilities);
349             assertEquals("Unexpected oomScoreAdj", oomScoreAdj, jobState.oomScoreAdj);
350         }
351     }
352 
353     boolean awaitJobScheduleResult(long maxWaitMs, int jobResult) throws Exception {
354         return awaitJobScheduleResult(mJobId, maxWaitMs, jobResult);
355     }
356 
357     boolean awaitJobScheduleResult(int jobId, long maxWaitMs, int jobResult) throws Exception {
358         return waitUntilTrue(maxWaitMs, () -> {
359             synchronized (mTestJobStates) {
360                 TestJobState jobState = mTestJobStates.get(jobId);
361                 return jobState != null && jobState.scheduleResult == jobResult;
362             }
363         });
364     }
365 
366     private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
367         final long deadLine = SystemClock.uptimeMillis() + maxWait;
368         do {
369             Thread.sleep(500);
370         } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
371         return condition.isTrue();
372     }
373 
374     JobParameters getLastParams() {
375         synchronized (mTestJobStates) {
376             TestJobState jobState = mTestJobStates.get(mJobId);
377             return jobState == null ? null : jobState.params;
378         }
379     }
380 
381     private static final class TestJobState {
382         int scheduleResult;
383         boolean running;
384         int procState;
385         int capabilities;
386         int oomScoreAdj;
387         JobParameters params;
388 
389         TestJobState() {
390             initState();
391         }
392 
393         private void reset() {
394             initState();
395         }
396 
397         private void initState() {
398             running = false;
399             procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
400             capabilities = ActivityManager.PROCESS_CAPABILITY_NONE;
401             oomScoreAdj = INVALID_ADJ;
402             scheduleResult = -1;
403         }
404     }
405 
406     private interface Condition {
407         boolean isTrue() throws Exception;
408     }
409 }
410