blob: 8c1772c96b88116d540de86148f45449d9b846dc [file] [log] [blame]
/*
* 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.assertEquals;
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.alarmtestapp.cts.common.FgsTester;
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.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.Ignore;
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 DeviceConfigStateHelper mActivityManagerDeviceConfigStateHelper =
new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
private DeviceConfigStateHelper mTareDeviceConfigStateHelper =
new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_TARE);
private volatile int mAlarmCount;
private volatile String mFgsResult;
private int mInitialSyncDisabledMode = -1;
private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
mFgsResult = intent.getStringExtra(FgsTester.EXTRA_FGS_START_RESULT);
Log.d(TAG, "Received Action: " + intent.getAction()
+ ", alarmCount: " + mAlarmCount
+ ", fgsResult " + mFgsResult
+ " at elapsed: " + SystemClock.elapsedRealtime());
}
};
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
mAlarmCount = 0;
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
mContext.registerReceiver(mAlarmStateReceiver, intentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
updateAlarmManagerConstants();
mActivityManagerDeviceConfigStateHelper
.set("bg_auto_restricted_bucket_on_bg_restricted", "false");
SystemUtil.runWithShellPermissionIdentity(() -> {
mInitialSyncDisabledMode = DeviceConfig.getSyncDisabledMode();
DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT);
});
AppOpsUtils.setOpMode(TEST_APP_PACKAGE, OPSTR_RUN_ANY_IN_BACKGROUND, MODE_IGNORED);
makeUidIdle();
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)
.setComponent(mAlarmScheduler)
.putExtra(TestAlarmScheduler.EXTRA_TEST_FGS, true)
.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));
}
@Ignore("Feature auto_restricted_bucket_on_bg_restricted is disabled right now")
@Test
public void testRepeatingAlarmAllowedWhenAutoRestrictedBucketFeatureOn() throws Exception {
mTareDeviceConfigStateHelper.set("enable_tare_mode", "0"); // Test requires app standby
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));
assertEquals("Fgs start wasn't successful from AlarmClock", "", mFgsResult);
}
@After
public void tearDown() throws Exception {
if (mInitialSyncDisabledMode != -1) {
SystemUtil.runWithShellPermissionIdentity(() ->
DeviceConfig.setSyncDisabledMode(mInitialSyncDisabledMode));
}
deleteAlarmManagerConstants();
AppOpsUtils.reset(TEST_APP_PACKAGE);
mActivityManagerDeviceConfigStateHelper.restoreOriginalValues();
mTareDeviceConfigStateHelper.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 {
SystemUtil.runShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
}
private void makeUidIdle() throws IOException {
SystemUtil.runShellCommand("cmd deviceidle tempwhitelist -r " + TEST_APP_PACKAGE);
SystemUtil.runShellCommand("am make-uid-idle " + TEST_APP_PACKAGE);
}
private void toggleAutoRestrictedBucketOnBgRestricted(boolean enable) {
mActivityManagerDeviceConfigStateHelper.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;
}
}