1 /*
2  * Copyright (C) 2018 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 package android.os.cts.batterysaving;
17 
18 import static android.os.cts.batterysaving.common.Values.APP_25_PACKAGE;
19 import static android.os.cts.batterysaving.common.Values.getRandomInt;
20 
21 import static com.android.compatibility.common.util.AmUtils.runKill;
22 import static com.android.compatibility.common.util.AmUtils.runMakeUidIdle;
23 import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
24 import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
25 import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
26 import static com.android.compatibility.common.util.TestUtils.waitUntil;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertTrue;
30 
31 import android.app.AlarmManager;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.os.Handler;
37 import android.os.Looper;
38 import android.os.SystemClock;
39 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
40 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest;
41 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.SetAlarmRequest;
42 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.StartServiceRequest;
43 import android.os.cts.batterysaving.common.Values;
44 import android.util.Log;
45 
46 import androidx.test.filters.LargeTest;
47 import androidx.test.filters.MediumTest;
48 import androidx.test.runner.AndroidJUnit4;
49 
50 import com.android.compatibility.common.util.ThreadUtils;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 
57 import java.io.IOException;
58 import java.util.concurrent.atomic.AtomicInteger;
59 
60 /**
61  * CTS for battery saver alarm throttling
62  *
63  atest $ANDROID_BUILD_TOP/cts/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
64  */
65 @MediumTest
66 @RunWith(AndroidJUnit4.class)
67 public class BatterySaverAlarmTest extends BatterySavingTestBase {
68     private static final String TAG = "BatterySaverAlarmTest";
69 
70     private static final long DEFAULT_WAIT = 1_000;
71     private static final long THROTTLED_WAIT = 5_000;
72 
73     // Tweaked alarm manager constants to facilitate testing
74     private static final long MIN_REPEATING_INTERVAL = 5_000;
75     private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
76     private static final long ALLOW_WHILE_IDLE_LONG_TIME = 20_000;
77     private static final long MIN_FUTURITY = 2_000;
78 
updateAlarmManagerConstants()79     private void updateAlarmManagerConstants() throws IOException {
80         putGlobalSetting("alarm_manager_constants",
81                 "min_interval=" + MIN_REPEATING_INTERVAL + ","
82                 + "min_futurity=" + MIN_FUTURITY + ","
83                 + "allow_while_idle_short_time=" + ALLOW_WHILE_IDLE_SHORT_TIME + ","
84                 + "allow_while_idle_long_time=" + ALLOW_WHILE_IDLE_LONG_TIME);
85     }
86 
resetAlarmManagerConstants()87     private void resetAlarmManagerConstants() throws IOException {
88         putGlobalSetting("alarm_manager_constants", "null");
89     }
90 
91     // Use a different broadcast action every time.
92     private final String ACTION = "BATTERY_SAVER_ALARM_TEST_ALARM_ACTION_" + Values.getRandomInt();
93 
94     private final AtomicInteger mAlarmCount = new AtomicInteger();
95 
96     private final BroadcastReceiver mAlarmReceiver = new BroadcastReceiver() {
97         @Override
98         public void onReceive(Context context, Intent intent) {
99             mAlarmCount.incrementAndGet();
100             Log.d(TAG, "Alarm received at " + SystemClock.elapsedRealtime());
101         }
102     };
103 
104     @Before
setUp()105     public void setUp() throws IOException {
106         updateAlarmManagerConstants();
107 
108         final IntentFilter filter = new IntentFilter(ACTION);
109         getContext().registerReceiver(mAlarmReceiver, filter, null,
110                 new Handler(Looper.getMainLooper()));
111     }
112 
113     @After
tearDown()114     public void tearDown() throws IOException {
115         resetAlarmManagerConstants();
116         getContext().unregisterReceiver(mAlarmReceiver);
117     }
118 
scheduleAlarm(String targetPackage, int type, long triggerMillis)119     private void scheduleAlarm(String targetPackage, int type, long triggerMillis)
120             throws Exception {
121         scheduleAlarm(targetPackage, type, triggerMillis, /*whileIdle=*/ true);
122     }
123 
scheduleAlarm(String targetPackage, int type, long triggerMillis, boolean whileIdle)124     private void scheduleAlarm(String targetPackage, int type, long triggerMillis,
125             boolean whileIdle) throws Exception {
126         Log.d(TAG, "Setting an alarm at " + triggerMillis + " (in "
127                 + (triggerMillis - SystemClock.elapsedRealtime()) + "ms)");
128         final SetAlarmRequest areq = SetAlarmRequest.newBuilder()
129                 .setIntentAction(ACTION)
130                 .setType(type)
131                 .setAllowWhileIdle(whileIdle)
132                 .setTriggerTime(triggerMillis)
133                 .build();
134         final Payload response = mRpc.sendRequest(targetPackage,
135                 Payload.newBuilder().setTestServiceRequest(
136                         TestServiceRequest.newBuilder().setSetAlarm(areq))
137                         .build());
138         assertTrue(response.hasTestServiceResponse()
139                 && response.getTestServiceResponse().getSetAlarmAck());
140     }
141 
142     /**
143      * Return a service in the target package.
144      */
startService(String targetPackage, boolean foreground)145     private String startService(String targetPackage, boolean foreground)
146             throws Exception {
147         final String action = "start_service_" + getRandomInt() + "_fg=" + foreground;
148 
149         final Payload response = mRpc.sendRequest(targetPackage,
150                 Payload.newBuilder().setTestServiceRequest(
151                         TestServiceRequest.newBuilder().setStartService(
152                                 StartServiceRequest.newBuilder()
153                                         .setForeground(foreground)
154                                         .setAction(action).build()
155                         )).build());
156         assertTrue(response.hasTestServiceResponse()
157                 && response.getTestServiceResponse().getStartServiceAck());
158         return action;
159     }
160 
stopService(String targetPackage)161     private void stopService(String targetPackage) throws Exception {
162         final Payload response = mRpc.sendRequest(targetPackage,
163                 Payload.newBuilder().setTestServiceRequest(
164                         TestServiceRequest.newBuilder().setStopService(true).build()).build());
165         assertTrue(response.hasTestServiceResponse()
166                 && response.getTestServiceResponse().getStopServiceAck());
167     }
168 
169 
forcePackageIntoBg(String packageName)170     private void forcePackageIntoBg(String packageName) throws Exception {
171         stopService(packageName);
172         runMakeUidIdle(packageName);
173         Thread.sleep(200);
174         runKill(packageName, /*wait=*/ true);
175         Thread.sleep(1000);
176     }
177 
178     @LargeTest
179     @Test
testAllowWhileIdleThrottled()180     public void testAllowWhileIdleThrottled() throws Exception {
181         final String targetPackage = APP_25_PACKAGE;
182 
183         runDumpsysBatteryUnplug();
184 
185         enableBatterySaver(true);
186 
187         forcePackageIntoBg(targetPackage);
188 
189         // First alarm shouldn't be throttled.
190         long now = SystemClock.elapsedRealtime();
191         final long triggerElapsed1 = now + MIN_FUTURITY;
192         scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed1);
193         ThreadUtils.sleepUntilRealtime(triggerElapsed1 + DEFAULT_WAIT);
194         assertEquals("Allow-while-idle alarm shouldn't be blocked in battery saver",
195                 1, mAlarmCount.get());
196 
197         // Second one should be throttled.
198         mAlarmCount.set(0);
199 
200         // Check that the alarm scheduled at triggerElapsed2
201         // fires between triggerElapsed2 and (triggerElapsed3+THROTTLED_WAIT).
202         now = SystemClock.elapsedRealtime();
203         final long triggerElapsed2 = now + ALLOW_WHILE_IDLE_SHORT_TIME;
204         final long triggerElapsed3 = now + ALLOW_WHILE_IDLE_LONG_TIME;
205         scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed2);
206 
207         // Check the time first before checking the alarm counter to avoid a
208         // situation when the alarm fires between sleepUntilRealtime and
209         // assertEquals.
210         while (true) {
211             Thread.sleep(DEFAULT_WAIT);
212 
213             final int alarmCount = mAlarmCount.get();
214             if (SystemClock.elapsedRealtime() < triggerElapsed2) {
215                 assertEquals("Follow up allow-while-idle alarm shouldn't go off "
216                         + "before short time",
217                         0, alarmCount);
218             } else {
219                 break;
220             }
221         }
222 
223         ThreadUtils.sleepUntilRealtime(triggerElapsed3 + THROTTLED_WAIT);
224         assertEquals("Follow-up allow-while-idle alarm should go off after long time",
225                 1, mAlarmCount.get());
226 
227         // Start an FG service, which should reset throttling.
228         mAlarmCount.set(0);
229 
230         startService(targetPackage, true);
231 
232         now = SystemClock.elapsedRealtime();
233         final long triggerElapsed4 = now + ALLOW_WHILE_IDLE_SHORT_TIME;
234         scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed4);
235         ThreadUtils.sleepUntilRealtime(triggerElapsed4 + DEFAULT_WAIT);
236         assertEquals("Allow-while-idle alarm shouldn't be throttled in battery saver"
237                 +" after FG service started",
238                 1, mAlarmCount.get());
239 
240         stopService(targetPackage);
241         // Battery saver off. Always use the short time.
242         enableBatterySaver(false);
243 
244         mAlarmCount.set(0);
245 
246         now = SystemClock.elapsedRealtime();
247         final long triggerElapsed5 = now + ALLOW_WHILE_IDLE_SHORT_TIME;
248         scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed5);
249         ThreadUtils.sleepUntilRealtime(triggerElapsed5 + DEFAULT_WAIT);
250         assertEquals("Allow-while-idle alarm shouldn't be throttled in battery saver"
251                         +" when BS is off",
252                 1, mAlarmCount.get());
253 
254         // One more time.
255         mAlarmCount.set(0);
256 
257         now = SystemClock.elapsedRealtime();
258         final long triggerElapsed6 = now + ALLOW_WHILE_IDLE_SHORT_TIME;
259         scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed6);
260         ThreadUtils.sleepUntilRealtime(triggerElapsed6 + DEFAULT_WAIT);
261         assertEquals("Allow-while-idle alarm shouldn't be throttled when BS is off",
262                 1, mAlarmCount.get());
263     }
264 
265     @LargeTest
266     @Test
testAlarmsThrottled()267     public void testAlarmsThrottled() throws Exception {
268         final String targetPackage = APP_25_PACKAGE;
269 
270         runDumpsysBatteryUnplug();
271 
272         enableBatterySaver(true);
273 
274         forcePackageIntoBg(targetPackage);
275 
276         {
277             // When battery saver is enabled, alarms should be blocked.
278             final long triggerElapsed = SystemClock.elapsedRealtime() + MIN_FUTURITY;
279             scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed,
280                     /* whileIdle=*/ false);
281             ThreadUtils.sleepUntilRealtime(triggerElapsed + DEFAULT_WAIT);
282             assertEquals("Non-while-idle alarm should be blocked in battery saver",
283                     0, mAlarmCount.get());
284         }
285 
286         // Start an FG service -> should unblock the alarm.
287         startService(targetPackage, true);
288 
289         waitUntil("Alarm should fire for an FG app",
290                 () -> mAlarmCount.get() == 1);
291 
292         stopService(targetPackage);
293         // Try again.
294         mAlarmCount.set(0);
295 
296         forcePackageIntoBg(targetPackage);
297 
298         // Try again.
299         // When battery saver is enabled, alarms should be blocked.
300         {
301             final long triggerElapsed = SystemClock.elapsedRealtime() + MIN_FUTURITY;
302             scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed,
303                     /* whileIdle=*/ false);
304             ThreadUtils.sleepUntilRealtime(triggerElapsed + DEFAULT_WAIT);
305             assertEquals("Non-while-idle alarm should be blocked in battery saver",
306                     0, mAlarmCount.get());
307         }
308 
309         // This time, disable EBS -> should unblock the alarm.
310         enableBatterySaver(false);
311         waitUntil("Allow-while-idle alarm should be blocked in battery saver",
312                 () -> mAlarmCount.get() == 1);
313     }
314 }
315