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.EXTRA_REQUEST_JOB_UID_STATE;
21 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STARTED;
22 import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
23 import static android.jobscheduler.cts.jobtestapp.TestJobService.INVALID_ADJ;
24 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_CAPABILITIES_KEY;
25 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_OOM_SCORE_ADJ_KEY;
26 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
27 import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PROC_STATE_KEY;
28 import static android.server.wm.WindowManagerState.STATE_RESUMED;
29 
30 import static org.junit.Assert.assertEquals;
31 
32 import android.app.ActivityManager;
33 import android.app.job.JobParameters;
34 import android.content.BroadcastReceiver;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.jobscheduler.cts.jobtestapp.TestActivity;
40 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
41 import android.os.SystemClock;
42 import android.os.UserHandle;
43 import android.server.wm.WindowManagerStateHelper;
44 import android.util.Log;
45 
46 import com.android.compatibility.common.util.CallbackAsserter;
47 import com.android.compatibility.common.util.SystemUtil;
48 
49 import java.util.Map;
50 
51 /**
52  * Common functions to interact with the test app.
53  */
54 class TestAppInterface {
55     private static final String TAG = TestAppInterface.class.getSimpleName();
56 
57     static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp";
58     private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity";
59     static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver";
60 
61     private final Context mContext;
62     private final int mJobId;
63 
64     /* accesses must be synchronized on itself */
65     private final TestJobState mTestJobState = new TestJobState();
66 
TestAppInterface(Context ctx, int jobId)67     TestAppInterface(Context ctx, int jobId) {
68         mContext = ctx;
69         mJobId = jobId;
70 
71         final IntentFilter intentFilter = new IntentFilter();
72         intentFilter.addAction(ACTION_JOB_STARTED);
73         intentFilter.addAction(ACTION_JOB_STOPPED);
74         mContext.registerReceiver(mReceiver, intentFilter);
75     }
76 
cleanup()77     void cleanup() {
78         final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS);
79         cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
80         cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
81         mContext.sendBroadcast(cancelJobsIntent);
82         closeActivity();
83         mContext.unregisterReceiver(mReceiver);
84         mTestJobState.reset();
85     }
86 
scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob)87     void scheduleJob(boolean allowWhileIdle, int requiredNetworkType, boolean asExpeditedJob)
88             throws Exception {
89         scheduleJob(
90                 Map.of(
91                         TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle,
92                         TestJobSchedulerReceiver.EXTRA_AS_EXPEDITED, asExpeditedJob
93                 ),
94                 Map.of(
95                         TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, requiredNetworkType
96                 ));
97     }
98 
scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)99     void scheduleJob(Map<String, Boolean> booleanExtras, Map<String, Integer> intExtras)
100             throws Exception {
101         final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
102         scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
103         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mJobId);
104         booleanExtras.forEach(scheduleJobIntent::putExtra);
105         intExtras.forEach(scheduleJobIntent::putExtra);
106         scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
107 
108         final CallbackAsserter resultBroadcastAsserter = CallbackAsserter.forBroadcast(
109                 new IntentFilter(TestJobSchedulerReceiver.ACTION_JOB_SCHEDULE_RESULT));
110         mContext.sendBroadcast(scheduleJobIntent);
111         resultBroadcastAsserter.assertCalled("Didn't get schedule job result broadcast",
112                 15 /* 15 seconds */);
113     }
114 
115     /** Asks (not forces) JobScheduler to run the job if constraints are met. */
runSatisfiedJob()116     void runSatisfiedJob() throws Exception {
117         SystemUtil.runShellCommand("cmd jobscheduler run -s"
118                 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mJobId);
119     }
120 
startAndKeepTestActivity()121     void startAndKeepTestActivity() {
122         startAndKeepTestActivity(false);
123     }
124 
startAndKeepTestActivity(boolean waitForResume)125     void startAndKeepTestActivity(boolean waitForResume) {
126         final Intent testActivity = new Intent();
127         testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
128         ComponentName testComponentName = new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY);
129         testActivity.setComponent(testComponentName);
130         mContext.startActivity(testActivity);
131         if (waitForResume) {
132             new WindowManagerStateHelper().waitForActivityState(testComponentName, STATE_RESUMED);
133         }
134     }
135 
closeActivity()136     void closeActivity() {
137         mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
138     }
139 
140     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
141         @Override
142         public void onReceive(Context context, Intent intent) {
143             Log.d(TAG, "Received action " + intent.getAction());
144             switch (intent.getAction()) {
145                 case ACTION_JOB_STARTED:
146                 case ACTION_JOB_STOPPED:
147                     final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
148                     Log.d(TAG, "JobId: " + params.getJobId());
149                     synchronized (mTestJobState) {
150                         mTestJobState.running = ACTION_JOB_STARTED.equals(intent.getAction());
151                         mTestJobState.jobId = params.getJobId();
152                         mTestJobState.params = params;
153                         if (intent.getBooleanExtra(EXTRA_REQUEST_JOB_UID_STATE, false)) {
154                             mTestJobState.procState = intent.getIntExtra(JOB_PROC_STATE_KEY,
155                                     ActivityManager.PROCESS_STATE_NONEXISTENT);
156                             mTestJobState.capabilities = intent.getIntExtra(JOB_CAPABILITIES_KEY,
157                                     ActivityManager.PROCESS_CAPABILITY_NONE);
158                             mTestJobState.oomScoreAdj = intent.getIntExtra(JOB_OOM_SCORE_ADJ_KEY,
159                                     INVALID_ADJ);
160                         }
161                     }
162                     break;
163             }
164         }
165     };
166 
awaitJobStart(long maxWait)167     boolean awaitJobStart(long maxWait) throws Exception {
168         return waitUntilTrue(maxWait, () -> {
169             synchronized (mTestJobState) {
170                 return (mTestJobState.jobId == mJobId) && mTestJobState.running;
171             }
172         });
173     }
174 
175     boolean awaitJobStop(long maxWait) throws Exception {
176         return waitUntilTrue(maxWait, () -> {
177             synchronized (mTestJobState) {
178                 return (mTestJobState.jobId == mJobId) && !mTestJobState.running;
179             }
180         });
181     }
182 
183     void assertJobUidState(int procState, int capabilities, int oomScoreAdj) {
184         synchronized (mTestJobState) {
185             assertEquals("procState expected=" + procStateToString(procState)
186                     + ",actual=" + procStateToString(mTestJobState.procState),
187                     procState, mTestJobState.procState);
188             assertEquals("capabilities expected=" + getCapabilitiesSummary(capabilities)
189                     + ",actual=" + getCapabilitiesSummary(mTestJobState.capabilities),
190                     capabilities, mTestJobState.capabilities);
191             assertEquals("Unexpected oomScoreAdj", oomScoreAdj, mTestJobState.oomScoreAdj);
192         }
193     }
194 
195     private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
196         final long deadLine = SystemClock.uptimeMillis() + maxWait;
197         do {
198             Thread.sleep(500);
199         } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
200         return condition.isTrue();
201     }
202 
203     JobParameters getLastParams() {
204         synchronized (mTestJobState) {
205             return mTestJobState.params;
206         }
207     }
208 
209     private static final class TestJobState {
210         int jobId;
211         boolean running;
212         int procState;
213         int capabilities;
214         int oomScoreAdj;
215         JobParameters params;
216 
217         TestJobState() {
218             initState();
219         }
220 
221         private void reset() {
222             initState();
223         }
224 
225         private void initState() {
226             running = false;
227             procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
228             capabilities = ActivityManager.PROCESS_CAPABILITY_NONE;
229             oomScoreAdj = INVALID_ADJ;
230         }
231     }
232 
233     private interface Condition {
234         boolean isTrue() throws Exception;
235     }
236 }
237