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