Home | History | Annotate | Download | only in cts
      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