summaryrefslogtreecommitdiff
path: root/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
blob: c51ed92d40d81ca7e4716b48290f547b78d9170e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.alarmmanager.cts;

import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND;
import static android.app.AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
import android.alarmmanager.util.AlarmManagerDeviceConfigHelper;
import android.alarmmanager.util.Utils;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.compatibility.common.util.AppOpsUtils;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
import com.android.compatibility.common.util.SystemUtil;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;

/**
 * Tests that apps put in forced app standby by the user do not get to run alarms while in the
 * background
 */
@AppModeFull
@LargeTest
@RunWith(AndroidJUnit4.class)
public class BackgroundRestrictedAlarmsTest {
    private static final String TAG = BackgroundRestrictedAlarmsTest.class.getSimpleName();
    private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
    private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";

    private static final long DEFAULT_WAIT = 1_000;
    private static final long POLL_INTERVAL = 200;
    private static final long MIN_REPEATING_INTERVAL = 10_000;
    private static final long APP_STANDBY_WINDOW = 10_000;
    private static final long APP_STANDBY_RESTRICTED_WINDOW = 10_000;

    private Context mContext;
    private ComponentName mAlarmScheduler;
    private AlarmManagerDeviceConfigHelper mConfigHelper = new AlarmManagerDeviceConfigHelper();
    private UiDevice mUiDevice;
    private DeviceConfigStateHelper mDeviceConfigStateHelper;
    private volatile int mAlarmCount;

    private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
            Log.d(TAG, "Received action " + intent.getAction()
                    + " elapsed: " + SystemClock.elapsedRealtime());

        }
    };

    @Before
    public void setUp() throws Exception {
        mContext = InstrumentationRegistry.getTargetContext();
        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
        mAlarmCount = 0;
        updateAlarmManagerConstants();
        mDeviceConfigStateHelper =
                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
        mDeviceConfigStateHelper.set("bg_auto_restricted_bucket_on_bg_restricted", "false");
        SystemUtil.runWithShellPermissionIdentity(() ->
                DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT));
        AppOpsUtils.setOpMode(TEST_APP_PACKAGE, OPSTR_RUN_ANY_IN_BACKGROUND, MODE_IGNORED);
        makeUidIdle();
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
        mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
        setAppStandbyBucket("active");
    }

    private void scheduleAlarm(int type, long triggerMillis, long interval) {
        final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
        setAlarmIntent.setComponent(mAlarmScheduler);
        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, type);
        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
        setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mContext.sendBroadcast(setAlarmIntent);
    }

    private void scheduleAlarmClock(long triggerRTC) {
        AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerRTC, null);

        final Intent setAlarmClockIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM_CLOCK);
        setAlarmClockIntent.setComponent(mAlarmScheduler);
        setAlarmClockIntent.putExtra(TestAlarmScheduler.EXTRA_ALARM_CLOCK_INFO, alarmInfo);
        mContext.sendBroadcast(setAlarmClockIntent);
    }

    private static int getMinExpectedExpirations(long now, long start, long interval) {
        if (now - start <= 1000) {
            return 0;
        }
        return 1 + (int)((now - start - 1000)/interval);
    }

    @Test
    public void testRepeatingAlarmBlocked() throws Exception {
        final long interval = MIN_REPEATING_INTERVAL;
        final long triggerElapsed = SystemClock.elapsedRealtime() + interval;
        scheduleAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed, interval);
        Thread.sleep(DEFAULT_WAIT);
        Thread.sleep(2 * interval);
        assertFalse("Alarm got triggered even under restrictions", waitForAlarms(1, DEFAULT_WAIT));
        Thread.sleep(interval);
        AppOpsUtils.setOpMode(TEST_APP_PACKAGE, OPSTR_RUN_ANY_IN_BACKGROUND, MODE_ALLOWED);
        // The alarm is due to go off about 3 times by now. Adding some tolerance just in case
        // an expiration is due right about now.
        final int minCount = getMinExpectedExpirations(SystemClock.elapsedRealtime(),
                triggerElapsed, interval);
        assertTrue("Alarm should have expired at least " + minCount
                + " times when restrictions were lifted", waitForAlarms(minCount, DEFAULT_WAIT));
    }

    @Test
    public void testRepeatingAlarmAllowedWhenAutoRestrictedBucketFeatureOn() throws Exception {
        final long interval = MIN_REPEATING_INTERVAL;
        final long triggerElapsed = SystemClock.elapsedRealtime() + interval;
        toggleAutoRestrictedBucketOnBgRestricted(false);
        scheduleAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed, interval);
        Thread.sleep(DEFAULT_WAIT);
        Thread.sleep(2 * interval);
        assertFalse("Alarm got triggered even under restrictions", waitForAlarms(1, DEFAULT_WAIT));
        Thread.sleep(interval);
        toggleAutoRestrictedBucketOnBgRestricted(true);
        // The alarm is due to go off about 3 times by now. Adding some tolerance just in case
        // an expiration is due right about now.
        final int minCount = getMinExpectedExpirations(SystemClock.elapsedRealtime(),
                triggerElapsed, interval);
        assertTrue("Alarm should have expired at least " + minCount
                + " times when restrictions were lifted", waitForAlarms(minCount, DEFAULT_WAIT));
    }

    @Test
    public void testAlarmClockNotBlocked() throws Exception {
        final long nowRTC = System.currentTimeMillis();
        final long waitInterval = 3_000;
        final long triggerRTC = nowRTC + waitInterval;

        AppOpsUtils.setUidMode(Utils.getPackageUid(TEST_APP_PACKAGE), OPSTR_SCHEDULE_EXACT_ALARM,
                MODE_ALLOWED);

        scheduleAlarmClock(triggerRTC);
        Thread.sleep(waitInterval);
        assertTrue("AlarmClock did not go off as scheduled when under restrictions",
                waitForAlarms(1, DEFAULT_WAIT));
    }

    @After
    public void tearDown() throws Exception {
        SystemUtil.runWithShellPermissionIdentity(() ->
                DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE));
        deleteAlarmManagerConstants();
        AppOpsUtils.reset(TEST_APP_PACKAGE);
        mDeviceConfigStateHelper.restoreOriginalValues();
        // Cancel any leftover alarms
        final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
        cancelAlarmsIntent.setComponent(mAlarmScheduler);
        mContext.sendBroadcast(cancelAlarmsIntent);
        mContext.unregisterReceiver(mAlarmStateReceiver);
        // Broadcast unregister may race with the next register in setUp
        Thread.sleep(DEFAULT_WAIT);
    }

    private void updateAlarmManagerConstants() {
        mConfigHelper.with("min_futurity", 0L)
                .with("min_interval", MIN_REPEATING_INTERVAL)
                .with("min_window", 0)
                .with("app_standby_window", APP_STANDBY_WINDOW)
                .with("app_standby_restricted_window", APP_STANDBY_RESTRICTED_WINDOW)
                .commitAndAwaitPropagation();
    }

    private void deleteAlarmManagerConstants() {
        mConfigHelper.restoreAll();
    }

    private void setAppStandbyBucket(String bucket) throws IOException {
        mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
    }

    private void makeUidIdle() throws IOException {
        mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -r " + TEST_APP_PACKAGE);
        mUiDevice.executeShellCommand("am make-uid-idle " + TEST_APP_PACKAGE);
    }

    private void toggleAutoRestrictedBucketOnBgRestricted(boolean enable) {
        mDeviceConfigStateHelper.set("bg_auto_restricted_bucket_on_bg_restricted",
                Boolean.toString(enable));
    }

    private boolean waitForAlarms(int expectedAlarms, long timeout) throws InterruptedException {
        final long deadLine = SystemClock.uptimeMillis() + timeout;
        int alarmCount;
        do {
            Thread.sleep(POLL_INTERVAL);
            alarmCount = mAlarmCount;
        } while (alarmCount < expectedAlarms && SystemClock.uptimeMillis() < deadLine);
        return alarmCount >= expectedAlarms;
    }
}