xref: /aosp_15_r20/cts/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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 package android.jobscheduler.cts;
17 
18 import static android.server.wm.WindowManagerState.STATE_RESUMED;
19 
20 import static com.android.compatibility.common.util.TestUtils.waitUntil;
21 
22 import android.annotation.CallSuper;
23 import android.annotation.TargetApi;
24 import android.app.Instrumentation;
25 import android.app.job.JobScheduler;
26 import android.content.ClipData;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.jobscheduler.MockJobService;
32 import android.jobscheduler.TestActivity;
33 import android.jobscheduler.TriggerContentJobService;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.PowerManager;
37 import android.os.Process;
38 import android.os.SystemClock;
39 import android.os.SystemProperties;
40 import android.os.UserHandle;
41 import android.provider.DeviceConfig;
42 import android.provider.Settings;
43 import android.server.wm.WindowManagerStateHelper;
44 import android.test.InstrumentationTestCase;
45 import android.util.Log;
46 
47 import com.android.compatibility.common.util.BatteryUtils;
48 import com.android.compatibility.common.util.DeviceConfigStateHelper;
49 import com.android.compatibility.common.util.SystemUtil;
50 
51 import java.io.IOException;
52 
53 /**
54  * Common functionality from which the other test case classes derive.
55  * TODO: b/338305140 - Move to JUnit4.
56  */
57 @TargetApi(21)
58 public abstract class BaseJobSchedulerTest extends InstrumentationTestCase {
59     private static final String TAG = BaseJobSchedulerTest.class.getSimpleName();
60     static final int HW_TIMEOUT_MULTIPLIER = SystemProperties.getInt("ro.hw_timeout_multiplier", 1);
61     static final int USER_ID = UserHandle.myUserId();
62 
63     /** Environment that notifies of JobScheduler callbacks. */
64     static MockJobService.TestEnvironment kTestEnvironment =
65             MockJobService.TestEnvironment.getTestEnvironment();
66     static TriggerContentJobService.TestEnvironment kTriggerTestEnvironment =
67             TriggerContentJobService.TestEnvironment.getTestEnvironment();
68     /** Handle for the service which receives the execution callbacks from the JobScheduler. */
69     static ComponentName kJobServiceComponent;
70     static ComponentName kTriggerContentServiceComponent;
71     JobScheduler mJobScheduler;
72 
73     Context mContext;
74     DeviceConfigStateHelper mDeviceConfigStateHelper;
75 
76     static final String MY_PACKAGE = "android.jobscheduler.cts";
77 
78     static final String JOBPERM_PACKAGE = "android.jobscheduler.cts.jobperm";
79     static final String JOBPERM_AUTHORITY = "android.jobscheduler.cts.jobperm.provider";
80     static final String JOBPERM_PERM = "android.jobscheduler.cts.jobperm.perm";
81 
82     Uri mFirstUri;
83     Bundle mFirstUriBundle;
84     Uri mSecondUri;
85     Bundle mSecondUriBundle;
86     ClipData mFirstClipData;
87     ClipData mSecondClipData;
88 
89     boolean mStorageStateChanged;
90     boolean mActivityStarted;
91 
92     private boolean mDeviceIdleEnabled;
93     private boolean mDeviceLightIdleEnabled;
94 
95     private String mInitialBatteryStatsConstants;
96 
97     @Override
injectInstrumentation(Instrumentation instrumentation)98     public void injectInstrumentation(Instrumentation instrumentation) {
99         super.injectInstrumentation(instrumentation);
100         mContext = instrumentation.getContext();
101         kJobServiceComponent = new ComponentName(getContext(), MockJobService.class);
102         kTriggerContentServiceComponent = new ComponentName(getContext(),
103                 TriggerContentJobService.class);
104         mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
105         mFirstUri = Uri.parse("content://" + JOBPERM_AUTHORITY + "/protected/foo");
106         mFirstUriBundle = new Bundle();
107         mFirstUriBundle.putParcelable("uri", mFirstUri);
108         mSecondUri = Uri.parse("content://" + JOBPERM_AUTHORITY + "/protected/bar");
109         mSecondUriBundle = new Bundle();
110         mSecondUriBundle.putParcelable("uri", mSecondUri);
111         mFirstClipData = new ClipData("JobPerm1", new String[] { "application/*" },
112                 new ClipData.Item(mFirstUri));
113         mSecondClipData = new ClipData("JobPerm2", new String[] { "application/*" },
114                 new ClipData.Item(mSecondUri));
115         try {
116             SystemUtil.runShellCommand(getInstrumentation(), "cmd activity set-inactive "
117                     + mContext.getPackageName() + " false");
118         } catch (IOException e) {
119             Log.w("ConstraintTest", "Failed setting inactive false", e);
120         }
121         if (HW_TIMEOUT_MULTIPLIER != 0) {
122             Log.i(TAG, "HW multiplier set to " + HW_TIMEOUT_MULTIPLIER);
123         }
124     }
125 
getContext()126     public Context getContext() {
127         return mContext;
128     }
129 
130     @CallSuper
131     @Override
setUp()132     public void setUp() throws Exception {
133         super.setUp();
134         mDeviceConfigStateHelper =
135                 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
136         SystemUtil.runShellCommand("cmd jobscheduler cache-config-changes on");
137         // Disable batching behavior.
138         mDeviceConfigStateHelper.set("min_ready_cpu_only_jobs_count", "0");
139         mDeviceConfigStateHelper.set("min_ready_non_active_jobs_count", "0");
140         mDeviceConfigStateHelper.set("conn_transport_batch_threshold", "");
141         // Disable flex behavior.
142         mDeviceConfigStateHelper.set("fc_applied_constraints", "0");
143         kTestEnvironment.setUp();
144         kTriggerTestEnvironment.setUp();
145         mJobScheduler.cancelAll();
146 
147         mDeviceIdleEnabled = isDeviceIdleEnabled();
148         mDeviceLightIdleEnabled = isDeviceLightIdleEnabled();
149         if (mDeviceIdleEnabled || mDeviceLightIdleEnabled) {
150             // Make sure the device isn't dozing since it will affect execution of regular jobs
151             setDeviceIdleState(false);
152         }
153 
154         mInitialBatteryStatsConstants = Settings.Global.getString(mContext.getContentResolver(),
155                 Settings.Global.BATTERY_STATS_CONSTANTS);
156         // Make sure ACTION_CHARGING is sent immediately.
157         Settings.Global.putString(mContext.getContentResolver(),
158                 Settings.Global.BATTERY_STATS_CONSTANTS, "battery_charged_delay_ms=0");
159     }
160 
161     @CallSuper
162     @Override
tearDown()163     public void tearDown() throws Exception {
164         SystemUtil.runShellCommand("cmd jobscheduler cache-config-changes off");
165         SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off");
166         SystemUtil.runShellCommand(getInstrumentation(), "cmd battery reset");
167         Settings.Global.putString(mContext.getContentResolver(),
168                 Settings.Global.BATTERY_STATS_CONSTANTS, mInitialBatteryStatsConstants);
169         if (mStorageStateChanged) {
170             // Put storage service back in to normal operation.
171             SystemUtil.runShellCommand(getInstrumentation(), "cmd devicestoragemonitor reset");
172             mStorageStateChanged = false;
173         }
174         SystemUtil.runShellCommand(getInstrumentation(),
175                 "cmd jobscheduler reset-execution-quota -u " + USER_ID + " "
176                         + kJobServiceComponent.getPackageName());
177         mDeviceConfigStateHelper.restoreOriginalValues();
178 
179         if (mActivityStarted) {
180             closeActivity();
181         }
182 
183         if (mDeviceIdleEnabled || mDeviceLightIdleEnabled) {
184             resetDeviceIdleState();
185         }
186 
187         // The super method should be called at the end.
188         super.tearDown();
189     }
190 
assertHasUriPermission(Uri uri, int grantFlags)191     public void assertHasUriPermission(Uri uri, int grantFlags) {
192         if ((grantFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
193             assertEquals(PackageManager.PERMISSION_GRANTED,
194                     getContext().checkUriPermission(uri, Process.myPid(),
195                             Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION));
196         }
197         if ((grantFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
198             assertEquals(PackageManager.PERMISSION_GRANTED,
199                     getContext().checkUriPermission(uri, Process.myPid(),
200                             Process.myUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
201         }
202     }
203 
waitPermissionRevoke(Uri uri, int access, long timeout)204     void waitPermissionRevoke(Uri uri, int access, long timeout) {
205         long startTime = SystemClock.elapsedRealtime();
206         while (getContext().checkUriPermission(uri, Process.myPid(), Process.myUid(), access)
207                 != PackageManager.PERMISSION_DENIED) {
208             try {
209                 Thread.sleep(50);
210             } catch (InterruptedException e) {
211             }
212             if ((SystemClock.elapsedRealtime()-startTime) >= timeout) {
213                 fail("Timed out waiting for permission revoke");
214             }
215         }
216     }
217 
isDeviceIdleFeatureEnabled()218     boolean isDeviceIdleFeatureEnabled() throws Exception {
219         return mDeviceIdleEnabled || mDeviceLightIdleEnabled;
220     }
221 
isDeviceIdleEnabled()222     static boolean isDeviceIdleEnabled() throws Exception {
223         final String output = SystemUtil.runShellCommand("cmd deviceidle enabled deep").trim();
224         return Integer.parseInt(output) != 0;
225     }
226 
isDeviceLightIdleEnabled()227     static boolean isDeviceLightIdleEnabled() throws Exception {
228         final String output = SystemUtil.runShellCommand("cmd deviceidle enabled light").trim();
229         return Integer.parseInt(output) != 0;
230     }
231 
232     /** Returns the current storage-low state, as believed by JobScheduler. */
isJsStorageStateLow()233     private boolean isJsStorageStateLow() throws Exception {
234         return !Boolean.parseBoolean(
235                 SystemUtil.runShellCommand(getInstrumentation(),
236                         "cmd jobscheduler get-storage-not-low").trim());
237     }
238 
239     // Note we are just using storage state as a way to control when the job gets executed.
setStorageStateLow(boolean low)240     void setStorageStateLow(boolean low) throws Exception {
241         if (isJsStorageStateLow() == low) {
242             // Nothing to do here
243             return;
244         }
245         mStorageStateChanged = true;
246         String res;
247         if (low) {
248             res = SystemUtil.runShellCommand(getInstrumentation(),
249                     "cmd devicestoragemonitor force-low -f");
250         } else {
251             res = SystemUtil.runShellCommand(getInstrumentation(),
252                     "cmd devicestoragemonitor force-not-low -f");
253         }
254         int seq = Integer.parseInt(res.trim());
255         long startTime = SystemClock.elapsedRealtime();
256 
257         // Wait for the storage update to be processed by job scheduler before proceeding.
258         int curSeq;
259         do {
260             curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
261                     "cmd jobscheduler get-storage-seq").trim());
262             if (curSeq == seq) {
263                 return;
264             }
265             Thread.sleep(500);
266         } while ((SystemClock.elapsedRealtime() - startTime) < 10_000);
267 
268         fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq);
269     }
270 
startAndKeepTestActivity()271     void startAndKeepTestActivity() {
272         final Intent testActivity = new Intent();
273         testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
274         ComponentName testComponentName = new ComponentName(mContext, TestActivity.class);
275         testActivity.setComponent(testComponentName);
276         mContext.startActivity(testActivity);
277         new WindowManagerStateHelper().waitForActivityState(testComponentName, STATE_RESUMED);
278         mActivityStarted = true;
279     }
280 
closeActivity()281     void closeActivity() {
282         mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
283         mActivityStarted = false;
284     }
285 
setDeviceConfigFlag(String key, String value, boolean waitForConfirmation)286     void setDeviceConfigFlag(String key, String value, boolean waitForConfirmation)
287             throws Exception {
288         mDeviceConfigStateHelper.set(key, value);
289         if (waitForConfirmation) {
290             waitUntil("Config didn't update appropriately to '" + value
291                             + "'. Current value=" + getConfigValue(key),
292                     5 /* seconds */,
293                     () -> {
294                         final String curVal = getConfigValue(key);
295                         if (value == null) {
296                             return "null".equals(curVal);
297                         } else {
298                             return curVal.equals(value);
299                         }
300                     });
301         }
302     }
303 
getConfigValue(String key)304     static String getConfigValue(String key) {
305         return SystemUtil.runShellCommand("cmd jobscheduler get-config-value " + key).trim();
306     }
307 
getJobState(int jobId)308     String getJobState(int jobId) throws Exception {
309         return SystemUtil.runShellCommand(getInstrumentation(),
310                 "cmd jobscheduler get-job-state --user " + USER_ID + " "
311                         + kJobServiceComponent.getPackageName() + " " + jobId).trim();
312     }
313 
assertJobReady(int jobId)314     void assertJobReady(int jobId) throws Exception {
315         String state = getJobState(jobId);
316         assertTrue("Job unexpectedly not ready, in state: " + state, state.contains("ready"));
317     }
318 
assertJobWaiting(int jobId)319     void assertJobWaiting(int jobId) throws Exception {
320         String state = getJobState(jobId);
321         assertTrue("Job unexpectedly not waiting, in state: " + state, state.contains("waiting"));
322     }
323 
assertJobNotReady(int jobId)324     void assertJobNotReady(int jobId) throws Exception {
325         String state = getJobState(jobId);
326         assertTrue("Job unexpectedly ready, in state: " + state, !state.contains("ready"));
327     }
328 
329     /**
330      * Set the screen state.
331      */
toggleScreenOn(final boolean screenon)332     static void toggleScreenOn(final boolean screenon) throws Exception {
333         BatteryUtils.turnOnScreen(screenon);
334         if (screenon) {
335             SystemUtil.runShellCommand("wm dismiss-keyguard");
336         }
337         // Wait a little bit for the broadcasts to be processed.
338         Thread.sleep(2_000);
339     }
340 
resetDeviceIdleState()341     void resetDeviceIdleState() throws Exception {
342         SystemUtil.runShellCommand("cmd deviceidle unforce");
343     }
344 
setBatteryState(boolean plugged, int level)345     void setBatteryState(boolean plugged, int level) throws Exception {
346         SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on");
347         if (plugged) {
348             SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
349             final int curLevel = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
350                     "dumpsys battery get level").trim());
351             if (curLevel >= level) {
352                 // Lower the level so when we set it to the desired level, JobScheduler thinks
353                 // the device is charging.
354                 SystemUtil.runShellCommand(getInstrumentation(),
355                         "cmd battery set level " + Math.max(1, level - 1));
356             }
357         } else {
358             SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug");
359         }
360         int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
361                 "cmd battery set -f level " + level).trim());
362 
363         // Wait for the battery update to be processed by job scheduler before proceeding.
364         waitUntil("JobScheduler didn't update charging status to " + plugged, 15 /* seconds */,
365                 () -> {
366                     int curSeq;
367                     boolean curCharging;
368                     curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
369                             "cmd jobscheduler get-battery-seq").trim());
370                     curCharging = Boolean.parseBoolean(
371                             SystemUtil.runShellCommand(getInstrumentation(),
372                                     "cmd jobscheduler get-battery-charging").trim());
373                     return curSeq >= seq && curCharging == plugged;
374                 });
375     }
376 
setDeviceIdleState(final boolean idle)377     void setDeviceIdleState(final boolean idle) throws Exception {
378         final String changeCommand;
379         if (idle) {
380             changeCommand = "force-idle " + (mDeviceIdleEnabled ? "deep" : "light");
381         } else {
382             changeCommand = "force-active";
383         }
384         SystemUtil.runShellCommand("cmd deviceidle " + changeCommand);
385         waitUntil("Could not change device idle state to " + idle, 15 /* seconds */,
386                 () -> {
387                     PowerManager powerManager = getContext().getSystemService(PowerManager.class);
388                     if (idle) {
389                         return mDeviceIdleEnabled
390                                 ? powerManager.isDeviceIdleMode()
391                                 : powerManager.isDeviceLightIdleMode();
392                     } else {
393                         return !powerManager.isDeviceIdleMode()
394                                 && !powerManager.isDeviceLightIdleMode();
395                     }
396                 });
397     }
398 
399     /** Asks (not forces) JobScheduler to run the job if constraints are met. */
runSatisfiedJob(int jobId)400     void runSatisfiedJob(int jobId) throws Exception {
401         runSatisfiedJob(jobId, null);
402     }
403 
runSatisfiedJob(int jobId, String namespace)404     void runSatisfiedJob(int jobId, String namespace) throws Exception {
405         if (HW_TIMEOUT_MULTIPLIER > 1) {
406             // Device has increased HW multiplier. Wait a short amount of time before sending the
407             // run command since there's a higher chance JobScheduler's processing is delayed.
408             Thread.sleep(1_000L);
409         }
410         SystemUtil.runShellCommand(getInstrumentation(),
411                 "cmd jobscheduler run -s"
412                 + " -u " + USER_ID
413                 + (namespace == null ? "" : " -n " + namespace)
414                 + " " + kJobServiceComponent.getPackageName()
415                 + " " + jobId);
416     }
417 }
418