blob: 882b2420edf877d89d99b55d65690f59fa4e99f7 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.jobscheduler.cts;
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.jobscheduler.MockJobService.TestEnvironment;
import android.jobscheduler.MockJobService.TestEnvironment.Event;
import android.provider.DeviceConfig;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.SystemUtil;
import java.util.List;
/**
* Tests related to scheduling jobs.
*/
@TargetApi(30)
public class JobSchedulingTest extends BaseJobSchedulerTest {
private static final int MIN_SCHEDULE_QUOTA = 250;
private static final int JOB_ID = JobSchedulingTest.class.hashCode();
// The maximum number of jobs that can run concurrently.
private static final int MAX_JOB_CONTEXTS_COUNT = 16;
@Override
public void tearDown() throws Exception {
mJobScheduler.cancel(JOB_ID);
SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler reset-schedule-quota");
SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off");
BatteryUtils.runDumpsysBatteryReset();
// The super method should be called at the end.
super.tearDown();
}
/**
* Test that apps can call schedule at least the minimum amount of times without being
* blocked.
*/
public void testMinSuccessfulSchedulingQuota() {
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setMinimumLatency(60 * 60 * 1000L)
.setPersisted(true)
.build();
for (int i = 0; i < MIN_SCHEDULE_QUOTA; ++i) {
assertEquals(JobScheduler.RESULT_SUCCESS, mJobScheduler.schedule(jobInfo));
}
}
/**
* Test that scheduling fails once an app hits the schedule quota limit.
*/
public void testFailingScheduleOnQuotaExceeded() {
mDeviceConfigStateHelper.set(
new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setBoolean("enable_api_quotas", true)
.setInt("aq_schedule_count", 300)
.setLong("aq_schedule_window_ms", 300000)
.setBoolean("aq_schedule_throw_exception", false)
.setBoolean("aq_schedule_return_failure", true)
.build());
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setMinimumLatency(60 * 60 * 1000L)
.setPersisted(true)
.build();
for (int i = 0; i < 500; ++i) {
final int expected =
i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
assertEquals("Got unexpected result for schedule #" + (i + 1),
expected, mJobScheduler.schedule(jobInfo));
}
}
/**
* Test that scheduling succeeds even after an app hits the schedule quota limit.
*/
public void testContinuingScheduleOnQuotaExceeded() {
mDeviceConfigStateHelper.set(
new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setBoolean("enable_api_quotas", true)
.setInt("aq_schedule_count", 300)
.setLong("aq_schedule_window_ms", 300000)
.setBoolean("aq_schedule_throw_exception", false)
.setBoolean("aq_schedule_return_failure", false)
.build());
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setMinimumLatency(60 * 60 * 1000L)
.setPersisted(true)
.build();
for (int i = 0; i < 500; ++i) {
assertEquals("Got unexpected result for schedule #" + (i + 1),
JobScheduler.RESULT_SUCCESS, mJobScheduler.schedule(jobInfo));
}
}
/**
* Test that non-persisted jobs aren't limited by quota.
*/
public void testNonPersistedJobsNotLimited() {
mDeviceConfigStateHelper.set(
new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setBoolean("enable_api_quotas", true)
.setInt("aq_schedule_count", 300)
.setLong("aq_schedule_window_ms", 60000)
.setBoolean("aq_schedule_throw_exception", false)
.setBoolean("aq_schedule_return_failure", true)
.build());
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setMinimumLatency(60 * 60 * 1000L)
.setPersisted(false)
.build();
for (int i = 0; i < 500; ++i) {
assertEquals(JobScheduler.RESULT_SUCCESS, mJobScheduler.schedule(jobInfo));
}
}
public void testHigherPriorityJobRunsFirst() throws Exception {
SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on");
setStorageStateLow(true);
final int higherPriorityJobId = JOB_ID;
final int numMinPriorityJobs = 2 * MAX_JOB_CONTEXTS_COUNT;
kTestEnvironment.setExpectedExecutions(1 + numMinPriorityJobs);
for (int i = 0; i < numMinPriorityJobs; ++i) {
JobInfo job = new JobInfo.Builder(higherPriorityJobId + 1 + i, kJobServiceComponent)
.setPriority(JobInfo.PRIORITY_MIN)
.setRequiresStorageNotLow(true)
.build();
mJobScheduler.schedule(job);
}
// Schedule the higher priority job last since the default sorting is by enqueue time.
JobInfo jobMax = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent)
.setPriority(JobInfo.PRIORITY_DEFAULT)
.setRequiresStorageNotLow(true)
.build();
mJobScheduler.schedule(jobMax);
setStorageStateLow(false);
kTestEnvironment.awaitExecution();
Event jobHigherExecution = new Event(TestEnvironment.Event.EVENT_START_JOB,
higherPriorityJobId);
List<Event> executedEvents = kTestEnvironment.getExecutedEvents();
boolean higherExecutedFirst = false;
// Due to racing, we can't just check the very first item in the array. We can however
// make sure it was in the first set of jobs to run.
for (int i = 0; i < executedEvents.size() && i < MAX_JOB_CONTEXTS_COUNT; ++i) {
if (executedEvents.get(i).equals(jobHigherExecution)) {
higherExecutedFirst = true;
break;
}
}
assertTrue(
"Higher priority job (" + higherPriorityJobId + ") didn't run in first batch: "
+ executedEvents, higherExecutedFirst);
}
}