1 /* 2 * Copyright (C) 2017 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 17 package android.jobscheduler.cts; 18 19 20 import android.Manifest; 21 import android.annotation.TargetApi; 22 import android.app.job.JobInfo; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.net.ConnectivityManager; 29 import android.net.NetworkInfo; 30 import android.net.wifi.WifiManager; 31 import android.os.BatteryManager; 32 import android.os.SystemClock; 33 import android.provider.Settings; 34 import android.util.Log; 35 36 import com.android.compatibility.common.util.SystemUtil; 37 38 import java.util.concurrent.CountDownLatch; 39 import java.util.concurrent.TimeUnit; 40 41 /** 42 * Schedules jobs with the {@link android.app.job.JobScheduler} that have battery constraints. 43 */ 44 @TargetApi(26) 45 public class BatteryConstraintTest extends ConstraintTest { 46 private static final String TAG = "BatteryConstraintTest"; 47 48 private String FEATURE_WATCH = "android.hardware.type.watch"; 49 private String TWM_HARDWARE_FEATURE = "com.google.clockwork.hardware.traditional_watch_mode"; 50 51 /** Unique identifier for the job scheduled by this suite of tests. */ 52 public static final int BATTERY_JOB_ID = BatteryConstraintTest.class.hashCode(); 53 54 private JobInfo.Builder mBuilder; 55 /** 56 * Record of the previous state of power save mode trigger level to reset it after the test 57 * finishes. 58 */ 59 private int mPreviousLowPowerTriggerLevel; 60 61 @Override 62 public void setUp() throws Exception { 63 super.setUp(); 64 65 // Disable power save mode as some devices may turn off Android when power save mode is 66 // enabled, causing the test to fail. 67 mPreviousLowPowerTriggerLevel = Settings.Global.getInt(getContext().getContentResolver(), 68 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1); 69 Settings.Global.putInt(getContext().getContentResolver(), 70 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); 71 72 mBuilder = new JobInfo.Builder(BATTERY_JOB_ID, kJobServiceComponent); 73 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on"); 74 } 75 76 @Override 77 public void tearDown() throws Exception { 78 mJobScheduler.cancel(BATTERY_JOB_ID); 79 // Put battery service back in to normal operation. 80 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off"); 81 SystemUtil.runShellCommand(getInstrumentation(), "cmd battery reset"); 82 83 // Reset power save mode to its previous state. 84 if (mPreviousLowPowerTriggerLevel == -1) { 85 Settings.Global.putString(getContext().getContentResolver(), 86 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, null); 87 } else { 88 Settings.Global.putInt(getContext().getContentResolver(), 89 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, mPreviousLowPowerTriggerLevel); 90 } 91 } 92 93 boolean hasBattery() throws Exception { 94 Intent batteryInfo = getContext().registerReceiver( 95 null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 96 boolean present = batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 97 if (!present) { 98 Log.i(TAG, "Device doesn't have a battery."); 99 } 100 return present; 101 } 102 103 void setBatteryState(boolean plugged, int level) throws Exception { 104 if (plugged) { 105 SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1"); 106 } else { 107 SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug"); 108 } 109 int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(), 110 "cmd battery set -f level " + level).trim()); 111 long startTime = SystemClock.elapsedRealtime(); 112 113 // Wait for the battery update to be processed by job scheduler before proceeding. 114 int curSeq; 115 boolean curCharging; 116 do { 117 Thread.sleep(50); 118 curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(), 119 "cmd jobscheduler get-battery-seq").trim()); 120 // The job scheduler actually looks at the charging/discharging state, 121 // which is currently determined by battery stats in response to the low-level 122 // plugged/unplugged events. So we can get this updated after the last seq 123 // is received, so we need to make sure that has correctly changed. 124 curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(), 125 "cmd jobscheduler get-battery-charging").trim()); 126 if (curSeq == seq && curCharging == plugged) { 127 return; 128 } 129 } while ((SystemClock.elapsedRealtime() - startTime) < 5000); 130 131 fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq 132 + ", plugged=" + plugged + " curCharging=" + curCharging); 133 } 134 135 void verifyChargingState(boolean charging) throws Exception { 136 boolean curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(), 137 "cmd jobscheduler get-battery-charging").trim()); 138 assertEquals(charging, curCharging); 139 } 140 141 void verifyBatteryNotLowState(boolean notLow) throws Exception { 142 boolean curNotLow = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(), 143 "cmd jobscheduler get-battery-not-low").trim()); 144 assertEquals(notLow, curNotLow); 145 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 146 Intent batteryState = getContext().registerReceiver(null, filter); 147 assertEquals(notLow, 148 !batteryState.getBooleanExtra(BatteryManager.EXTRA_BATTERY_LOW, notLow)); 149 } 150 151 String getJobState() throws Exception { 152 return getJobState(BATTERY_JOB_ID); 153 } 154 155 void assertJobReady() throws Exception { 156 assertJobReady(BATTERY_JOB_ID); 157 } 158 159 void assertJobWaiting() throws Exception { 160 assertJobWaiting(BATTERY_JOB_ID); 161 } 162 163 void assertJobNotReady() throws Exception { 164 assertJobNotReady(BATTERY_JOB_ID); 165 } 166 167 static void waitFor(long waitMillis) throws Exception { 168 final long deadline = SystemClock.uptimeMillis() + waitMillis; 169 do { 170 Thread.sleep(500L); 171 } while (SystemClock.uptimeMillis() < deadline); 172 } 173 174 // -------------------------------------------------------------------------------------------- 175 // Positives - schedule jobs under conditions that require them to pass. 176 // -------------------------------------------------------------------------------------------- 177 178 /** 179 * Schedule a job that requires the device is charging, when the battery reports it is 180 * plugged in. 181 */ 182 public void testChargingConstraintExecutes() throws Exception { 183 setBatteryState(true, 100); 184 verifyChargingState(true); 185 186 kTestEnvironment.setExpectedExecutions(1); 187 kTestEnvironment.setExpectedWaitForRun(); 188 mJobScheduler.schedule(mBuilder.setRequiresCharging(true).build()); 189 assertJobReady(); 190 kTestEnvironment.readyToRun(); 191 192 assertTrue("Job with charging constraint did not fire on power.", 193 kTestEnvironment.awaitExecution()); 194 } 195 196 /** 197 * Schedule a job that requires the device is not critical, when the battery reports it is 198 * plugged in. 199 */ 200 public void testBatteryNotLowConstraintExecutes_withPower() throws Exception { 201 setBatteryState(true, 100); 202 waitFor(2_000); 203 verifyChargingState(true); 204 verifyBatteryNotLowState(true); 205 206 kTestEnvironment.setExpectedExecutions(1); 207 kTestEnvironment.setExpectedWaitForRun(); 208 mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build()); 209 assertJobReady(); 210 kTestEnvironment.readyToRun(); 211 212 assertTrue("Job with battery not low constraint did not fire on power.", 213 kTestEnvironment.awaitExecution()); 214 } 215 216 /** 217 * Schedule a job that requires the device is not critical, when the battery reports it is 218 * not plugged in but has sufficient power. 219 */ 220 public void testBatteryNotLowConstraintExecutes_withoutPower() throws Exception { 221 // "Without power" test case is valid only for devices with a battery. 222 if (!hasBattery()) { 223 return; 224 } 225 226 setBatteryState(false, 100); 227 waitFor(2_000); 228 verifyChargingState(false); 229 verifyBatteryNotLowState(true); 230 231 kTestEnvironment.setExpectedExecutions(1); 232 kTestEnvironment.setExpectedWaitForRun(); 233 mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build()); 234 assertJobReady(); 235 kTestEnvironment.readyToRun(); 236 237 assertTrue("Job with battery not low constraint did not fire on power.", 238 kTestEnvironment.awaitExecution()); 239 } 240 241 // -------------------------------------------------------------------------------------------- 242 // Negatives - schedule jobs under conditions that require that they fail. 243 // -------------------------------------------------------------------------------------------- 244 245 /** 246 * Schedule a job that requires the device is charging, and assert if failed when 247 * the device is not on power. 248 */ 249 public void testChargingConstraintFails() throws Exception { 250 // "Without power" test case is valid only for devices with a battery. 251 if (!hasBattery()) { 252 return; 253 } 254 255 setBatteryState(false, 100); 256 verifyChargingState(false); 257 258 kTestEnvironment.setExpectedExecutions(0); 259 kTestEnvironment.setExpectedWaitForRun(); 260 mJobScheduler.schedule(mBuilder.setRequiresCharging(true).build()); 261 assertJobWaiting(); 262 assertJobNotReady(); 263 kTestEnvironment.readyToRun(); 264 265 assertFalse("Job with charging constraint fired while not on power.", 266 kTestEnvironment.awaitExecution(250)); 267 assertJobWaiting(); 268 assertJobNotReady(); 269 270 // Ensure the job runs once the device is plugged in. 271 kTestEnvironment.setExpectedExecutions(1); 272 kTestEnvironment.setExpectedWaitForRun(); 273 kTestEnvironment.setContinueAfterStart(); 274 setBatteryState(true, 100); 275 verifyChargingState(true); 276 kTestEnvironment.setExpectedStopped(); 277 assertJobReady(); 278 kTestEnvironment.readyToRun(); 279 assertTrue("Job with charging constraint did not fire on power.", 280 kTestEnvironment.awaitExecution()); 281 282 // And check that the job is stopped if the device is unplugged while it is running. 283 setBatteryState(false, 100); 284 verifyChargingState(false); 285 assertTrue("Job with charging constraint did not stop when power removed.", 286 kTestEnvironment.awaitStopped()); 287 } 288 289 /** 290 * Schedule a job that requires the device is not critical, and assert it failed when 291 * the battery level is critical and not on power. 292 */ 293 public void testBatteryNotLowConstraintFails_withoutPower() throws Exception { 294 // "Without power" test case is valid only for devices with a battery. 295 if (!hasBattery()) { 296 return; 297 } 298 if(getInstrumentation().getContext().getPackageManager().hasSystemFeature(FEATURE_WATCH) && 299 getInstrumentation().getContext().getPackageManager().hasSystemFeature( 300 TWM_HARDWARE_FEATURE)) { 301 return; 302 } 303 304 setBatteryState(false, 5); 305 // setBatteryState() waited for the charging/not-charging state to formally settle, 306 // but battery level reporting lags behind that. wait a moment to let that happen 307 // before proceeding. 308 waitFor(2_000); 309 verifyChargingState(false); 310 verifyBatteryNotLowState(false); 311 312 kTestEnvironment.setExpectedExecutions(0); 313 kTestEnvironment.setExpectedWaitForRun(); 314 mJobScheduler.schedule(mBuilder.setRequiresBatteryNotLow(true).build()); 315 assertJobWaiting(); 316 assertJobNotReady(); 317 kTestEnvironment.readyToRun(); 318 319 assertFalse("Job with battery not low constraint fired while level critical.", 320 kTestEnvironment.awaitExecution(250)); 321 assertJobWaiting(); 322 assertJobNotReady(); 323 324 // Ensure the job runs once the device's battery level is not low. 325 kTestEnvironment.setExpectedExecutions(1); 326 kTestEnvironment.setExpectedWaitForRun(); 327 kTestEnvironment.setContinueAfterStart(); 328 setBatteryState(false, 50); 329 waitFor(2_000); 330 verifyChargingState(false); 331 verifyBatteryNotLowState(true); 332 kTestEnvironment.setExpectedStopped(); 333 assertJobReady(); 334 kTestEnvironment.readyToRun(); 335 assertTrue("Job with not low constraint did not fire when charge increased.", 336 kTestEnvironment.awaitExecution()); 337 338 // And check that the job is stopped if battery goes low again. 339 setBatteryState(false, 5); 340 setBatteryState(false, 4); 341 waitFor(2_000); 342 verifyChargingState(false); 343 verifyBatteryNotLowState(false); 344 assertTrue("Job with not low constraint did not stop when battery went low.", 345 kTestEnvironment.awaitStopped()); 346 } 347 } 348