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.alarmmanager.cts;
     18 
     19 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
     20 
     21 import static org.junit.Assert.assertFalse;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.junit.Assume.assumeTrue;
     24 
     25 import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
     26 import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
     27 import android.content.BroadcastReceiver;
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentFilter;
     32 import android.os.BatteryManager;
     33 import android.os.SystemClock;
     34 import android.support.test.InstrumentationRegistry;
     35 import android.support.test.filters.LargeTest;
     36 import android.support.test.runner.AndroidJUnit4;
     37 import android.support.test.uiautomator.UiDevice;
     38 import android.util.Log;
     39 
     40 import com.android.compatibility.common.util.AppStandbyUtils;
     41 
     42 import org.junit.After;
     43 import org.junit.AfterClass;
     44 import org.junit.Before;
     45 import org.junit.BeforeClass;
     46 import org.junit.Test;
     47 import org.junit.runner.RunWith;
     48 
     49 import java.io.IOException;
     50 import java.util.concurrent.atomic.AtomicInteger;
     51 
     52 /**
     53  * Tests that app standby imposes the appropriate restrictions on alarms
     54  */
     55 @LargeTest
     56 @RunWith(AndroidJUnit4.class)
     57 public class AppStandbyTests {
     58     private static final String TAG = AppStandbyTests.class.getSimpleName();
     59     private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
     60     private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
     61 
     62     private static final long DEFAULT_WAIT = 4_000;
     63     private static final long POLL_INTERVAL = 200;
     64 
     65     // Tweaked alarm manager constants to facilitate testing
     66     private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 15_000;
     67     private static final long MIN_FUTURITY = 2_000;
     68     private static final long[] APP_STANDBY_DELAYS = {0, 10_000, 20_000, 30_000, 600_000};
     69     private static final String[] APP_BUCKET_TAGS = {
     70             "active",
     71             "working_set",
     72             "frequent",
     73             "rare",
     74             "never"
     75     };
     76     private static final String[] APP_BUCKET_KEYS = {
     77             "standby_active_delay",
     78             "standby_working_delay",
     79             "standby_frequent_delay",
     80             "standby_rare_delay",
     81             "standby_never_delay",
     82     };
     83 
     84     // Save the state before running tests to restore it after we finish testing.
     85     private static boolean sOrigAppStandbyEnabled;
     86 
     87     private Context mContext;
     88     private ComponentName mAlarmScheduler;
     89     private UiDevice mUiDevice;
     90     private AtomicInteger mAlarmCount;
     91     private volatile long mLastAlarmTime;
     92 
     93     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
     94         @Override
     95         public void onReceive(Context context, Intent intent) {
     96             mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1));
     97             mLastAlarmTime = SystemClock.elapsedRealtime();
     98             Log.d(TAG, "No. of expirations: " + mAlarmCount
     99                     + " elapsed: " + SystemClock.elapsedRealtime());
    100         }
    101     };
    102 
    103     @BeforeClass
    104     public static void setUpTests() throws Exception {
    105         sOrigAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabledAtRuntime();
    106         if (!sOrigAppStandbyEnabled) {
    107             AppStandbyUtils.setAppStandbyEnabledAtRuntime(true);
    108 
    109             // Give system sometime to initialize itself.
    110             Thread.sleep(100);
    111         }
    112     }
    113 
    114     @Before
    115     public void setUp() throws Exception {
    116         mContext = InstrumentationRegistry.getTargetContext();
    117         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    118         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
    119         mAlarmCount = new AtomicInteger(0);
    120         updateAlarmManagerConstants();
    121         setBatteryCharging(false);
    122         final IntentFilter intentFilter = new IntentFilter();
    123         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
    124         mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
    125         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
    126         setAppStandbyBucket("active");
    127         scheduleAlarm(SystemClock.elapsedRealtime(), false, 0);
    128         Thread.sleep(MIN_FUTURITY);
    129         assertTrue("Alarm not sent when app in active", waitForAlarms(1));
    130     }
    131 
    132     private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
    133         final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
    134         setAlarmIntent.setComponent(mAlarmScheduler);
    135         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
    136         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
    137         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
    138         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle);
    139         setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    140         mContext.sendBroadcast(setAlarmIntent);
    141     }
    142 
    143     private void testBucketDelay(int bucketIndex) throws Exception {
    144         setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
    145         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
    146         final long minTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[bucketIndex];
    147         scheduleAlarm(triggerTime, false, 0);
    148         Thread.sleep(MIN_FUTURITY);
    149         if (triggerTime + DEFAULT_WAIT < minTriggerTime) {
    150             assertFalse("Alarm went off before " + APP_BUCKET_TAGS[bucketIndex] + " delay",
    151                     waitForAlarms(1));
    152             Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
    153         }
    154         assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay",
    155                 waitForAlarms(1));
    156     }
    157 
    158     @Test
    159     public void testWorkingSetDelay() throws Exception {
    160         testBucketDelay(1);
    161     }
    162 
    163     @Test
    164     public void testFrequentDelay() throws Exception {
    165         testBucketDelay(2);
    166     }
    167 
    168     @Test
    169     public void testRareDelay() throws Exception {
    170         testBucketDelay(3);
    171     }
    172 
    173     @Test
    174     public void testBucketUpgradeToSmallerDelay() throws Exception {
    175         setAppStandbyBucket(APP_BUCKET_TAGS[2]);
    176         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
    177         final long workingSetExpectedTrigger = mLastAlarmTime + APP_STANDBY_DELAYS[1];
    178         scheduleAlarm(triggerTime, false, 0);
    179         Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime());
    180         assertFalse("The alarm went off before frequent delay", waitForAlarms(1));
    181         setAppStandbyBucket(APP_BUCKET_TAGS[1]);
    182         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
    183                 waitForAlarms(1));
    184     }
    185 
    186 
    187     /**
    188      * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket
    189      * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for.
    190      * The alarm must then go off as soon as possible - at either the scheduled time or the bucket
    191      * change, whichever happened later.
    192      */
    193     @Test
    194     public void testBucketUpgradeToNoDelay() throws Exception {
    195         setAppStandbyBucket(APP_BUCKET_TAGS[3]);
    196         final long triggerTime1 = mLastAlarmTime + APP_STANDBY_DELAYS[2];
    197         scheduleAlarm(triggerTime1, false, 0);
    198         Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime());
    199         assertFalse("The alarm went off after frequent delay when app in rare bucket",
    200                 waitForAlarms(1));
    201         setAppStandbyBucket(APP_BUCKET_TAGS[1]);
    202         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
    203                 waitForAlarms(1));
    204 
    205         // Once more
    206         setAppStandbyBucket(APP_BUCKET_TAGS[3]);
    207         final long triggerTime2 = mLastAlarmTime + APP_STANDBY_DELAYS[2];
    208         scheduleAlarm(triggerTime2, false, 0);
    209         setAppStandbyBucket(APP_BUCKET_TAGS[0]);
    210         Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
    211         assertTrue("The alarm did not go off as scheduled when the app was in active",
    212                 waitForAlarms(1));
    213     }
    214 
    215     @Test
    216     public void testAllowWhileIdleAlarms() throws Exception {
    217         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
    218         scheduleAlarm(firstTrigger, true, 0);
    219         Thread.sleep(MIN_FUTURITY);
    220         assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarms(1));
    221         scheduleAlarm(mLastAlarmTime + 9_000, true, 0);
    222         // First check for the case where allow_while_idle delay should supersede app standby
    223         setAppStandbyBucket(APP_BUCKET_TAGS[1]);
    224         Thread.sleep(APP_STANDBY_DELAYS[1]);
    225         assertFalse("allow_while_idle alarm went off before short time", waitForAlarms(1));
    226         long expectedTriggerTime = mLastAlarmTime + ALLOW_WHILE_IDLE_SHORT_TIME;
    227         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
    228         assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarms(1));
    229 
    230         // Now the other case, app standby delay supersedes the allow_while_idle delay
    231         scheduleAlarm(mLastAlarmTime + 12_000, true, 0);
    232         setAppStandbyBucket(APP_BUCKET_TAGS[2]);
    233         Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME);
    234         assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[2]
    235                 + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1));
    236         expectedTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[2];
    237         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
    238         assertTrue("allow_while_idle alarm did not go off even after " + APP_STANDBY_DELAYS[2]
    239                 + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1));
    240     }
    241 
    242     @Test
    243     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
    244         setAppStandbyBucket(APP_BUCKET_TAGS[3]);
    245         setPowerWhitelisted(true);
    246         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
    247         scheduleAlarm(triggerTime, false, 0);
    248         Thread.sleep(MIN_FUTURITY);
    249         assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarms(1));
    250         setPowerWhitelisted(false);
    251     }
    252 
    253     @After
    254     public void tearDown() throws Exception {
    255         setPowerWhitelisted(false);
    256         setBatteryCharging(true);
    257         deleteAlarmManagerConstants();
    258         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
    259         cancelAlarmsIntent.setComponent(mAlarmScheduler);
    260         mContext.sendBroadcast(cancelAlarmsIntent);
    261         mContext.unregisterReceiver(mAlarmStateReceiver);
    262         // Broadcast unregister may race with the next register in setUp
    263         Thread.sleep(500);
    264     }
    265 
    266     @AfterClass
    267     public static void tearDownTests() throws Exception {
    268         if (!sOrigAppStandbyEnabled) {
    269             AppStandbyUtils.setAppStandbyEnabledAtRuntime(sOrigAppStandbyEnabled);
    270         }
    271     }
    272 
    273     private void updateAlarmManagerConstants() throws IOException {
    274         final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
    275         cmd.append("min_futurity="); cmd.append(MIN_FUTURITY);
    276         cmd.append(",allow_while_idle_short_time="); cmd.append(ALLOW_WHILE_IDLE_SHORT_TIME);
    277         for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) {
    278             cmd.append(",");
    279             cmd.append(APP_BUCKET_KEYS[i]); cmd.append("="); cmd.append(APP_STANDBY_DELAYS[i]);
    280         }
    281         executeAndLog(cmd.toString());
    282     }
    283 
    284     private void setPowerWhitelisted(boolean whitelist) throws IOException {
    285         final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist ");
    286         cmd.append(whitelist ? "+" : "-");
    287         cmd.append(TEST_APP_PACKAGE);
    288         executeAndLog(cmd.toString());
    289     }
    290 
    291     private void deleteAlarmManagerConstants() throws IOException {
    292         executeAndLog("settings delete global alarm_manager_constants");
    293     }
    294 
    295     private void setAppStandbyBucket(String bucket) throws IOException {
    296         executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
    297     }
    298 
    299     private void setBatteryCharging(final boolean charging) throws Exception {
    300         final BatteryManager bm = mContext.getSystemService(BatteryManager.class);
    301         final String cmd = "dumpsys battery " + (charging ? "reset" : "unplug");
    302         executeAndLog(cmd);
    303         if (!charging) {
    304             assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000));
    305         }
    306     }
    307 
    308     private String executeAndLog(String cmd) throws IOException {
    309         final String output = mUiDevice.executeShellCommand(cmd).trim();
    310         Log.d(TAG, "command: [" + cmd + "], output: [" + output + "]");
    311         return output;
    312     }
    313 
    314     private boolean waitForAlarms(final int numAlarms) throws InterruptedException {
    315         final boolean success = waitUntil(() -> (mAlarmCount.get() == numAlarms), DEFAULT_WAIT);
    316         mAlarmCount.set(0);
    317         return success;
    318     }
    319 
    320     private boolean waitUntil(Condition condition, long timeout) throws InterruptedException {
    321         final long deadLine = SystemClock.uptimeMillis() + timeout;
    322         while (!condition.isMet() && SystemClock.uptimeMillis() < deadLine) {
    323             Thread.sleep(POLL_INTERVAL);
    324         }
    325         return condition.isMet();
    326     }
    327 
    328     @FunctionalInterface
    329     interface Condition {
    330         boolean isMet();
    331     }
    332 }
    333