| /* |
| * 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()); |
| } |
| } |