blob: f1dca8b15dc50edcee0c39d36a5b354fc8027554 [file] [log] [blame]
/*
* Copyright (C) 2017 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.Manifest;
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import com.android.compatibility.common.util.SystemUtil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Schedules jobs with the {@link android.app.job.JobScheduler} that have battery constraints.
*/
@TargetApi(26)
public class BatteryConstraintTest extends ConstraintTest {
private static final String TAG = "BatteryConstraintTest";
private String FEATURE_WATCH = "android.hardware.type.watch";
private String TWM_HARDWARE_FEATURE = "com.google.clockwork.hardware.traditional_watch_mode";
/** Unique identifier for the job scheduled by this suite of tests. */
public static final int BATTERY_JOB_ID = BatteryConstraintTest.class.hashCode();
private JobInfo.Builder mBuilder;
/**
* Record of the previous state of power save mode trigger level to reset it after the test
* finishes.
*/
private int mPreviousLowPowerTriggerLevel;
@Override
public void setUp() throws Exception {
super.setUp();
// Disable power save mode as some devices may turn off Android when power save mode is
// enabled, causing the test to fail.
mPreviousLowPowerTriggerLevel = Settings.Global.getInt(getContext().getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1);
Settings.Global.putInt(getContext().getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
mBuilder = new JobInfo.Builder(BATTERY_JOB_ID, kJobServiceComponent);
SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on");
}
@Override
public void tearDown() throws Exception {
mJobScheduler.cancel(BATTERY_JOB_ID);
// Put battery service back in to normal operation.
SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off");
SystemUtil.runShellCommand(getInstrumentation(), "cmd battery reset");
// Reset power save mode to its previous state.
if (mPreviousLowPowerTriggerLevel == -1) {
Settings.Global.putString(getContext().getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, null);
} else {
Settings.Global.putInt(getContext().getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, mPreviousLowPowerTriggerLevel);
}
}
void setBatteryState(boolean plugged, int level) throws Exception {
if (plugged) {
SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
} else {
SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug");
}
int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
"cmd battery set -f level " + level).trim());
long startTime = SystemClock.elapsedRealtime();
// Wait for the battery update to be processed by job scheduler before proceeding.
int curSeq;
boolean curCharging;
do {
Thread.sleep(50);
curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
"cmd jobscheduler get-battery-seq").trim());
// The job scheduler actually looks at the charging/discharging state,
// which is currently determined by battery stats in response to the low-level
// plugged/unplugged events. So we can get this updated after the last seq
// is received, so we need to make sure that has correctly changed.
curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
"cmd jobscheduler get-battery-charging").trim());
if (curSeq == seq && curCharging == plugged) {
return;
}
} while ((SystemClock.elapsedRealtime() - startTime) < 5000);
fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq
+ ", plugged=" + plugged + " curCharging=" + curCharging);
}
void verifyChargingState(boolean charging) throws Exception {
boolean curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
"cmd jobscheduler get-battery-charging").trim());
assertEquals(charging, curCharging);
}
void verifyBatteryNotLowState(boolean notLow) throws Exception {
boolean curNotLow = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
"cmd jobscheduler get-battery-not-low").trim());
assertEquals(notLow, curNotLow);
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryState = getContext().registerReceiver(null, filter);
assertEquals(notLow,
!batteryState.getBooleanExtra(BatteryManager.EXTRA_BATTERY_LOW, notLow));
}
String getJobState() throws Exception {
return getJobState(BATTERY_JOB_ID);
}
void assertJobReady() throws Exception {
assertJobReady(BATTERY_JOB_ID);
}
void assertJobWaiting() throws Exception {
assertJobWaiting(BATTERY_JOB_ID);
}
void assertJobNotReady() throws Exception {
assertJobNotReady(BATTERY_JOB_ID);
}
static void waitFor(long waitMillis) throws Exception {
final long deadline = SystemClock.uptimeMillis() + waitMillis;
do {
Thread.sleep(500L);
} while (SystemClock.uptimeMillis() < deadline);
}
// --------------------------------------------------------------------------------------------
// Positives - schedule jobs under conditions that require them to pass.
// --------------------------------------------------------------------------------------------
/**
* Schedule a job that requires the device is charging, when the battery reports it is
* plugged in.
*/
public void testChargingConstraintExecutes() throws Exception {
setBatteryState(true, 100);
verifyChargingState(true);
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresCharging(true).build());
assertJobReady();
kTestEnvironment.readyToRun();
assertTrue("Job with charging constraint did not fire on power.",
kTestEnvironment.awaitExecution());
}
/**
* Schedule a job that requires the device is not critical, when the battery reports it is
* plugged in.
*/
public void testBatteryNotLowConstraintExecutes_withPower() throws Exception {
setBatteryState(true, 100);
waitFor(2_000);
verifyChargingState(true);
verifyBatteryNotLowState(true);
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build());
assertJobReady();
kTestEnvironment.readyToRun();
assertTrue("Job with battery not low constraint did not fire on power.",
kTestEnvironment.awaitExecution());
}
/**
* Schedule a job that requires the device is not critical, when the battery reports it is
* not plugged in but has sufficient power.
*/
public void testBatteryNotLowConstraintExecutes_withoutPower() throws Exception {
setBatteryState(false, 100);
waitFor(2_000);
verifyChargingState(false);
verifyBatteryNotLowState(true);
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build());
assertJobReady();
kTestEnvironment.readyToRun();
assertTrue("Job with battery not low constraint did not fire on power.",
kTestEnvironment.awaitExecution());
}
// --------------------------------------------------------------------------------------------
// Negatives - schedule jobs under conditions that require that they fail.
// --------------------------------------------------------------------------------------------
/**
* Schedule a job that requires the device is charging, and assert if failed when
* the device is not on power.
*/
public void testChargingConstraintFails() throws Exception {
setBatteryState(false, 100);
verifyChargingState(false);
kTestEnvironment.setExpectedExecutions(0);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresCharging(true).build());
assertJobWaiting();
assertJobNotReady();
kTestEnvironment.readyToRun();
assertFalse("Job with charging constraint fired while not on power.",
kTestEnvironment.awaitExecution(250));
assertJobWaiting();
assertJobNotReady();
// Ensure the job runs once the device is plugged in.
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
kTestEnvironment.setContinueAfterStart();
setBatteryState(true, 100);
verifyChargingState(true);
kTestEnvironment.setExpectedStopped();
assertJobReady();
kTestEnvironment.readyToRun();
assertTrue("Job with charging constraint did not fire on power.",
kTestEnvironment.awaitExecution());
// And check that the job is stopped if the device is unplugged while it is running.
setBatteryState(false, 100);
verifyChargingState(false);
assertTrue("Job with charging constraint did not stop when power removed.",
kTestEnvironment.awaitStopped());
}
/**
* Schedule a job that requires the device is not critical, and assert it failed when
* the battery level is critical and not on power.
*/
public void testBatteryNotLowConstraintFails_withoutPower() throws Exception {
if(getInstrumentation().getContext().getPackageManager().hasSystemFeature(FEATURE_WATCH) &&
getInstrumentation().getContext().getPackageManager().hasSystemFeature(
TWM_HARDWARE_FEATURE)) {
return;
}
setBatteryState(false, 5);
// setBatteryState() waited for the charging/not-charging state to formally settle,
// but battery level reporting lags behind that. wait a moment to let that happen
// before proceeding.
waitFor(2_000);
verifyChargingState(false);
verifyBatteryNotLowState(false);
kTestEnvironment.setExpectedExecutions(0);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build());
assertJobWaiting();
assertJobNotReady();
kTestEnvironment.readyToRun();
assertFalse("Job with battery not low constraint fired while level critical.",
kTestEnvironment.awaitExecution(250));
assertJobWaiting();
assertJobNotReady();
// Ensure the job runs once the device's battery level is not low.
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
kTestEnvironment.setContinueAfterStart();
setBatteryState(false, 50);
waitFor(2_000);
verifyChargingState(false);
verifyBatteryNotLowState(true);
kTestEnvironment.setExpectedStopped();
assertJobReady();
kTestEnvironment.readyToRun();
assertTrue("Job with not low constraint did not fire when charge increased.",
kTestEnvironment.awaitExecution());
// And check that the job is stopped if battery goes low again.
setBatteryState(false, 5);
setBatteryState(false, 4);
waitFor(2_000);
verifyChargingState(false);
verifyBatteryNotLowState(false);
assertTrue("Job with not low constraint did not stop when battery went low.",
kTestEnvironment.awaitStopped());
}
}