• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static android.app.AppOpsManager.MODE_IGNORED;
21 import static android.app.AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM;
22 
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assume.assumeTrue;
26 
27 import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
28 import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
29 import android.alarmmanager.util.AlarmManagerDeviceConfigHelper;
30 import android.alarmmanager.util.Utils;
31 import android.app.Activity;
32 import android.content.BroadcastReceiver;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.os.BatteryManager;
38 import android.os.SystemClock;
39 import android.platform.test.annotations.AppModeFull;
40 import android.provider.DeviceConfig;
41 import android.support.test.uiautomator.UiDevice;
42 import android.util.Log;
43 import android.util.LongArray;
44 
45 import androidx.test.InstrumentationRegistry;
46 import androidx.test.filters.LargeTest;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import com.android.compatibility.common.util.AppOpsUtils;
50 import com.android.compatibility.common.util.AppStandbyUtils;
51 import com.android.compatibility.common.util.DeviceConfigStateHelper;
52 
53 import org.junit.After;
54 import org.junit.AfterClass;
55 import org.junit.Before;
56 import org.junit.BeforeClass;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 import java.io.IOException;
61 import java.util.concurrent.CountDownLatch;
62 import java.util.concurrent.TimeUnit;
63 import java.util.concurrent.atomic.AtomicInteger;
64 import java.util.function.BooleanSupplier;
65 
66 /**
67  * Tests that app standby imposes the appropriate restrictions on alarms
68  */
69 @AppModeFull
70 @LargeTest
71 @RunWith(AndroidJUnit4.class)
72 public class AppStandbyTests {
73     private static final String TAG = AppStandbyTests.class.getSimpleName();
74     static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
75     private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
76 
77     private static final long DEFAULT_WAIT = 2_000;
78     private static final long POLL_INTERVAL = 200;
79 
80     // Tweaked alarm manager constants to facilitate testing
81     private static final long MIN_FUTURITY = 1_000;
82 
83     // Not touching ACTIVE and RARE parameters for this test
84     private static final int WORKING_INDEX = 0;
85     private static final int FREQUENT_INDEX = 1;
86     private static final int RARE_INDEX = 2;
87     private static final String[] APP_BUCKET_TAGS = {
88             "working_set",
89             "frequent",
90             "rare",
91     };
92 
93     private static final long APP_STANDBY_WINDOW = 10_000;
94     private static final long MIN_WINDOW = 100;
95     private static final String[] APP_BUCKET_QUOTA_KEYS = {
96             "standby_quota_working",
97             "standby_quota_frequent",
98             "standby_quota_rare",
99     };
100     private static final int[] APP_STANDBY_QUOTAS = {
101             5,  // Working set
102             3,  // Frequent
103             1,  // Rare
104     };
105 
106     // Save the state before running tests to restore it after we finish testing.
107     private static boolean sOrigAppStandbyEnabled;
108     // Test app's alarm history to help predict when a subsequent alarm is going to get deferred.
109     private static TestAlarmHistory sAlarmHistory;
110     // Make sure TARE isn't enabled for any of these tests.
111     private static final DeviceConfigStateHelper sTareDeviceConfigStateHelper =
112             new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_TARE);
113     private static Context sContext = InstrumentationRegistry.getTargetContext();
114     private static UiDevice sUiDevice = UiDevice.getInstance(
115             InstrumentationRegistry.getInstrumentation());
116 
117     private ComponentName mAlarmScheduler;
118     private AtomicInteger mAlarmCount;
119     private AlarmManagerDeviceConfigHelper mConfigHelper = new AlarmManagerDeviceConfigHelper();
120 
121     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
122         @Override
123         public void onReceive(Context context, Intent intent) {
124             mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1));
125             final long nowElapsed = SystemClock.elapsedRealtime();
126             sAlarmHistory.addTime(nowElapsed);
127             Log.d(TAG, "No. of expirations: " + mAlarmCount + " elapsed: " + nowElapsed);
128         }
129     };
130 
131     @BeforeClass
setUpTests()132     public static void setUpTests() throws Exception {
133         sAlarmHistory = new TestAlarmHistory();
134         sOrigAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabledAtRuntime();
135         if (!sOrigAppStandbyEnabled) {
136             AppStandbyUtils.setAppStandbyEnabledAtRuntime(true);
137 
138             // Give system sometime to initialize itself.
139             Thread.sleep(100);
140         }
141         // These tests are designed for the old quota system.
142         sTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
143     }
144 
145     @Before
setUp()146     public void setUp() throws Exception {
147         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
148         mAlarmCount = new AtomicInteger(0);
149 
150         // To make sure it doesn't get pinned to working_set on older versions.
151         AppOpsUtils.setUidMode(Utils.getPackageUid(TEST_APP_PACKAGE), OPSTR_SCHEDULE_EXACT_ALARM,
152                 MODE_IGNORED);
153 
154         final IntentFilter intentFilter = new IntentFilter();
155         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
156         sContext.registerReceiver(mAlarmStateReceiver, intentFilter,
157                 Context.RECEIVER_EXPORTED_UNAUDITED);
158 
159         setBatteryCharging(false);
160         updateAlarmManagerConstants();
161         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
162     }
163 
scheduleAlarm(long triggerMillis, long interval)164     private void scheduleAlarm(long triggerMillis, long interval) throws InterruptedException {
165         final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
166         setAlarmIntent.setComponent(mAlarmScheduler);
167         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
168         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
169         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_WINDOW_LENGTH, MIN_WINDOW);
170         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
171         setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
172         final CountDownLatch resultLatch = new CountDownLatch(1);
173         sContext.sendOrderedBroadcast(setAlarmIntent, null, new BroadcastReceiver() {
174             @Override
175             public void onReceive(Context context, Intent intent) {
176                 resultLatch.countDown();
177             }
178         }, null, Activity.RESULT_CANCELED, null, null);
179         assertTrue("Request did not complete", resultLatch.await(10, TimeUnit.SECONDS));
180     }
181 
testSimpleQuotaDeferral(int bucketIndex)182     public void testSimpleQuotaDeferral(int bucketIndex) throws Exception {
183         setTestAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
184         final int quota = APP_STANDBY_QUOTAS[bucketIndex];
185 
186         long startElapsed = SystemClock.elapsedRealtime();
187         final long freshWindowPoint = sAlarmHistory.getLast(1) + APP_STANDBY_WINDOW;
188         if (freshWindowPoint > startElapsed) {
189             Thread.sleep(freshWindowPoint - startElapsed);
190             startElapsed = freshWindowPoint;
191             // Now we should have no alarms in the past APP_STANDBY_WINDOW
192         }
193         final long desiredTrigger = startElapsed + APP_STANDBY_WINDOW;
194         final long firstTrigger = startElapsed + 4_000;
195         assertTrue("Quota too large for test",
196                 firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
197         for (int i = 0; i < quota; i++) {
198             final long trigger = firstTrigger + (i * MIN_FUTURITY);
199             scheduleAlarm(trigger, 0);
200             Thread.sleep(trigger - SystemClock.elapsedRealtime());
201             assertTrue("Alarm within quota not firing as expected", waitForAlarm());
202         }
203 
204         // Now quota is reached, any subsequent alarm should get deferred.
205         scheduleAlarm(desiredTrigger, 0);
206         Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
207         assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
208         final long minTrigger = firstTrigger + APP_STANDBY_WINDOW;
209         Thread.sleep(minTrigger - SystemClock.elapsedRealtime());
210         assertTrue("Alarm exceeding quota not delivered after expected delay", waitForAlarm());
211     }
212 
213     @Test
testActiveQuota()214     public void testActiveQuota() throws Exception {
215         setTestAppStandbyBucket("active");
216         long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
217         for (int i = 0; i < 3; i++) {
218             scheduleAlarm(nextTrigger, 0);
219             Thread.sleep(MIN_FUTURITY);
220             assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
221             nextTrigger += MIN_FUTURITY;
222         }
223     }
224 
225     @Test
testWorkingQuota()226     public void testWorkingQuota() throws Exception {
227         testSimpleQuotaDeferral(WORKING_INDEX);
228     }
229 
230     @Test
testFrequentQuota()231     public void testFrequentQuota() throws Exception {
232         testSimpleQuotaDeferral(FREQUENT_INDEX);
233     }
234 
235     @Test
testRareQuota()236     public void testRareQuota() throws Exception {
237         testSimpleQuotaDeferral(RARE_INDEX);
238     }
239 
240     @Test
testNeverQuota()241     public void testNeverQuota() throws Exception {
242         setTestAppStandbyBucket("never");
243         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
244         scheduleAlarm(expectedTrigger, 0);
245         Thread.sleep(10_000);
246         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
247     }
248 
249     @Test
testPowerWhitelistedAlarmNotBlocked()250     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
251         setTestAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
252         setPowerWhitelisted(true);
253         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
254         scheduleAlarm(triggerTime, 0);
255         Thread.sleep(MIN_FUTURITY);
256         assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
257         setPowerWhitelisted(false);
258     }
259 
260     @After
tearDown()261     public void tearDown() throws Exception {
262         setPowerWhitelisted(false);
263         setBatteryCharging(true);
264         mConfigHelper.restoreAll();
265         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
266         cancelAlarmsIntent.setComponent(mAlarmScheduler);
267         sContext.sendBroadcast(cancelAlarmsIntent);
268         sContext.unregisterReceiver(mAlarmStateReceiver);
269         // Broadcast unregister may race with the next register in setUp
270         Thread.sleep(500);
271     }
272 
273     @AfterClass
tearDownTests()274     public static void tearDownTests() throws Exception {
275         if (!sOrigAppStandbyEnabled) {
276             AppStandbyUtils.setAppStandbyEnabledAtRuntime(sOrigAppStandbyEnabled);
277         }
278         sTareDeviceConfigStateHelper.restoreOriginalValues();
279     }
280 
updateAlarmManagerConstants()281     private void updateAlarmManagerConstants() {
282         mConfigHelper.with("min_futurity", MIN_FUTURITY)
283                 .with("app_standby_window", APP_STANDBY_WINDOW)
284                 .with("min_window", MIN_WINDOW)
285                 .with("exact_alarm_deny_list", TEST_APP_PACKAGE);
286         for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
287             mConfigHelper.with(APP_BUCKET_QUOTA_KEYS[i], APP_STANDBY_QUOTAS[i]);
288         }
289         mConfigHelper.commitAndAwaitPropagation();
290     }
291 
setPowerWhitelisted(boolean whitelist)292     private void setPowerWhitelisted(boolean whitelist) throws IOException {
293         final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist ");
294         cmd.append(whitelist ? "+" : "-");
295         cmd.append(TEST_APP_PACKAGE);
296         executeAndLog(cmd.toString());
297     }
298 
setTestAppStandbyBucket(String bucket)299     static void setTestAppStandbyBucket(String bucket) throws IOException {
300         executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
301     }
302 
setBatteryCharging(final boolean charging)303     private void setBatteryCharging(final boolean charging) throws Exception {
304         final BatteryManager bm = sContext.getSystemService(BatteryManager.class);
305         if (charging) {
306             executeAndLog("dumpsys battery reset");
307         } else {
308             executeAndLog("dumpsys battery unplug");
309             executeAndLog("dumpsys battery set status " +
310                     BatteryManager.BATTERY_STATUS_DISCHARGING);
311             assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000));
312         }
313     }
314 
executeAndLog(String cmd)315     private static String executeAndLog(String cmd) throws IOException {
316         final String output = sUiDevice.executeShellCommand(cmd).trim();
317         Log.d(TAG, "command: [" + cmd + "], output: [" + output + "]");
318         return output;
319     }
320 
waitForAlarm()321     private boolean waitForAlarm() throws InterruptedException {
322         final boolean success = waitUntil(() -> (mAlarmCount.get() == 1), DEFAULT_WAIT);
323         mAlarmCount.set(0);
324         return success;
325     }
326 
waitUntil(BooleanSupplier condition, long timeout)327     private boolean waitUntil(BooleanSupplier condition, long timeout) throws InterruptedException {
328         final long deadLine = SystemClock.uptimeMillis() + timeout;
329         while (!condition.getAsBoolean() && SystemClock.uptimeMillis() < deadLine) {
330             Thread.sleep(POLL_INTERVAL);
331         }
332         return condition.getAsBoolean();
333     }
334 
335     private static final class TestAlarmHistory {
336         private LongArray mHistory = new LongArray();
337 
addTime(long timestamp)338         private synchronized void addTime(long timestamp) {
339             mHistory.add(timestamp);
340         }
341 
342         /**
343          * Get the xth alarm time from the end.
344          */
getLast(int x)345         private synchronized long getLast(int x) {
346             if (x == 0 || x > mHistory.size()) {
347                 return 0;
348             }
349             return mHistory.get(mHistory.size() - x);
350         }
351     }
352 }
353