| /* |
| * Copyright (C) 2021 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.alarmmanager.cts.AppStandbyTests.setTestAppStandbyBucket; |
| import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; |
| import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeFalse; |
| |
| import android.alarmmanager.alarmtestapp.cts.PermissionStateChangedReceiver; |
| import android.alarmmanager.alarmtestapp.cts.policy_permission.RequestReceiver; |
| import android.alarmmanager.alarmtestapp.cts.policy_permission_32.RequestReceiverSdk32; |
| import android.alarmmanager.alarmtestapp.cts.sdk30.TestReceiver; |
| import android.alarmmanager.util.AlarmManagerDeviceConfigHelper; |
| import android.app.Activity; |
| import android.app.AlarmManager; |
| import android.app.AppOpsManager; |
| import android.app.PendingIntent; |
| import android.app.compat.CompatChanges; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.net.Uri; |
| import android.os.PowerWhitelistManager; |
| import android.os.Process; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.platform.test.annotations.AppModeFull; |
| import android.provider.Settings; |
| import android.util.Log; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.compatibility.common.util.AppOpsUtils; |
| import com.android.compatibility.common.util.AppStandbyUtils; |
| import com.android.compatibility.common.util.FeatureUtil; |
| import com.android.compatibility.common.util.ShellUtils; |
| import com.android.compatibility.common.util.SystemUtil; |
| import com.android.compatibility.common.util.TestUtils; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.Description; |
| import org.junit.runner.RunWith; |
| |
| import java.io.IOException; |
| import java.util.Random; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| @AppModeFull |
| @RunWith(AndroidJUnit4.class) |
| public class ExactAlarmsTest { |
| /** |
| * TODO (b/182835530): Add more tests for the following: |
| * |
| * Pre-S apps can: |
| * - use setAlarmClock freely -- no temp-allowlist |
| * - use setExactAndAWI with 7 / hr quota with standby and temp-allowlist |
| * - use setInexactAndAWI with 7 / hr quota with standby-bucket "ACTIVE" and temp-allowlist |
| * |
| * S+ apps with permission can: |
| * - use setInexactAWI with low quota + standby and *no* temp-allowlist. |
| */ |
| private static final String TAG = ExactAlarmsTest.class.getSimpleName(); |
| |
| private static final int ALLOW_WHILE_IDLE_QUOTA = 5; |
| private static final long ALLOW_WHILE_IDLE_WINDOW = 10_000; |
| private static final int ALLOW_WHILE_IDLE_COMPAT_QUOTA = 3; |
| |
| /** |
| * Waiting generously long for success because the system can sometimes be slow to |
| * provide expected behavior. |
| * A different and shorter duration should be used while waiting for no-failure, because |
| * even if the system is slow to fail in some cases, it would still cause some |
| * flakiness and get flagged for investigation. |
| */ |
| private static final long DEFAULT_WAIT_FOR_SUCCESS = 30_000; |
| |
| private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts"; |
| |
| private static final Context sContext = InstrumentationRegistry.getTargetContext(); |
| private final AlarmManager mAlarmManager = sContext.getSystemService(AlarmManager.class); |
| private final AppOpsManager mAppOpsManager = sContext.getSystemService(AppOpsManager.class); |
| private final PowerWhitelistManager mWhitelistManager = sContext.getSystemService( |
| PowerWhitelistManager.class); |
| private final PackageManager mPackageManager = sContext.getPackageManager(); |
| private final ComponentName mPermissionChangeReceiver = new ComponentName(TEST_APP_PACKAGE, |
| PermissionStateChangedReceiver.class.getName()); |
| |
| private final AlarmManagerDeviceConfigHelper mDeviceConfigHelper = |
| new AlarmManagerDeviceConfigHelper(); |
| private final Random mIdGenerator = new Random(6789); |
| |
| @Rule |
| public DumpLoggerRule mFailLoggerRule = new DumpLoggerRule(TAG) { |
| @Override |
| protected void failed(Throwable e, Description description) { |
| super.failed(e, description); |
| AlarmReceiver.dumpState(); |
| } |
| }; |
| |
| @Before |
| @After |
| public void resetAppOp() throws IOException { |
| AppOpsUtils.reset(sContext.getOpPackageName()); |
| AppOpsUtils.reset(TEST_APP_PACKAGE); |
| } |
| |
| @Before |
| public void updateAlarmManagerConstants() { |
| mDeviceConfigHelper.with("min_futurity", 0L) |
| .with("allow_while_idle_quota", ALLOW_WHILE_IDLE_QUOTA) |
| .with("allow_while_idle_compat_quota", ALLOW_WHILE_IDLE_COMPAT_QUOTA) |
| .with("allow_while_idle_window", ALLOW_WHILE_IDLE_WINDOW) |
| .with("crash_non_clock_apps", true) |
| .with("kill_on_schedule_exact_alarm_revoked", false) |
| .commitAndAwaitPropagation(); |
| } |
| |
| @Before |
| public void putDeviceToIdle() { |
| SystemUtil.runShellCommandForNoOutput("dumpsys battery reset"); |
| SystemUtil.runShellCommand("cmd deviceidle force-idle deep"); |
| } |
| |
| @Before |
| public void enableChange() { |
| if (!CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION)) { |
| SystemUtil.runShellCommand("am compat enable --no-kill REQUIRE_EXACT_ALARM_PERMISSION " |
| + sContext.getOpPackageName(), output -> output.contains("Enabled")); |
| } |
| } |
| |
| @After |
| public void resetChanges() { |
| // This is needed because compat persists the overrides beyond package uninstall |
| SystemUtil.runShellCommand("am compat reset --no-kill REQUIRE_EXACT_ALARM_PERMISSION " |
| + sContext.getOpPackageName()); |
| } |
| |
| @After |
| public void removeFromWhitelists() { |
| removeFromWhitelists(sContext.getOpPackageName()); |
| } |
| |
| private void removeFromWhitelists(String packageName) { |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> mWhitelistManager.removeFromWhitelist(packageName)); |
| SystemUtil.runShellCommand("cmd deviceidle tempwhitelist -r " + packageName); |
| } |
| |
| @After |
| public void restoreBatteryState() { |
| SystemUtil.runShellCommand("cmd deviceidle unforce"); |
| SystemUtil.runShellCommandForNoOutput("dumpsys battery reset"); |
| } |
| |
| @After |
| public void restorePermissionReceiverState() { |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> mPackageManager.setComponentEnabledSetting(mPermissionChangeReceiver, |
| PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, |
| PackageManager.DONT_KILL_APP)); |
| } |
| |
| @After |
| public void restoreAlarmManagerConstants() { |
| mDeviceConfigHelper.restoreAll(); |
| } |
| |
| private void revokeAppOp() { |
| revokeAppOp(sContext.getOpPackageName()); |
| } |
| |
| private void revokeAppOp(String packageName) { |
| setAppOp(packageName, AppOpsManager.MODE_IGNORED); |
| } |
| |
| private void setAppOp(String packageName, int mode) { |
| final int uid = getPackageUid(packageName); |
| |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> { |
| mAppOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, uid, mode); |
| return null; |
| } |
| ); |
| } |
| |
| private int getPackageUid(String packageName) { |
| try { |
| return sContext.getPackageManager().getPackageUid(packageName, 0); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static PendingIntent getAlarmSender(int id, boolean quotaed) { |
| final Intent alarmAction = new Intent(AlarmReceiver.ALARM_ACTION) |
| .setClass(sContext, AlarmReceiver.class) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
| .putExtra(AlarmReceiver.EXTRA_ALARM_ID, id) |
| .putExtra(AlarmReceiver.EXTRA_QUOTAED, quotaed); |
| return PendingIntent.getBroadcast(sContext, 0, alarmAction, |
| PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); |
| } |
| |
| @Test |
| public void hasPermissionByDefault() { |
| assertTrue(mAlarmManager.canScheduleExactAlarms()); |
| |
| mDeviceConfigHelper.with("exact_alarm_deny_list", sContext.getOpPackageName()) |
| .commitAndAwaitPropagation(); |
| assertFalse(mAlarmManager.canScheduleExactAlarms()); |
| } |
| |
| @Test |
| // TODO (b/185181884): Remove once standby buckets can be reliably manipulated from tests. |
| @Ignore("Cannot reliably test bucket manipulation yet") |
| public void exactAlarmPermissionElevatesBucket() throws Exception { |
| mDeviceConfigHelper.without("exact_alarm_deny_list").commitAndAwaitPropagation(); |
| |
| setTestAppStandbyBucket("active"); |
| assertEquals(STANDBY_BUCKET_ACTIVE, AppStandbyUtils.getAppStandbyBucket(TEST_APP_PACKAGE)); |
| |
| setTestAppStandbyBucket("frequent"); |
| assertEquals(STANDBY_BUCKET_WORKING_SET, |
| AppStandbyUtils.getAppStandbyBucket(TEST_APP_PACKAGE)); |
| |
| setTestAppStandbyBucket("rare"); |
| assertEquals(STANDBY_BUCKET_WORKING_SET, |
| AppStandbyUtils.getAppStandbyBucket(TEST_APP_PACKAGE)); |
| } |
| |
| @Test |
| public void noPermissionWhenIgnored() throws IOException { |
| revokeAppOp(); |
| assertFalse(mAlarmManager.canScheduleExactAlarms()); |
| } |
| |
| @Test |
| public void hasPermissionWhenAllowed() throws IOException { |
| setAppOp(sContext.getOpPackageName(), AppOpsManager.MODE_ALLOWED); |
| assertTrue(mAlarmManager.canScheduleExactAlarms()); |
| |
| mDeviceConfigHelper.with("exact_alarm_deny_list", sContext.getOpPackageName()) |
| .commitAndAwaitPropagation(); |
| assertTrue(mAlarmManager.canScheduleExactAlarms()); |
| } |
| |
| @Test |
| public void canScheduleExactAlarmWhenChangeDisabled() throws Exception { |
| final CountDownLatch resultLatch = new CountDownLatch(1); |
| final AtomicBoolean apiResult = new AtomicBoolean(false); |
| final AtomicInteger result = new AtomicInteger(-1); |
| |
| // Test app Targets sdk 30, so the change should be disabled. |
| final Intent requestToTestApp = new Intent(TestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM) |
| .setClassName(TestReceiver.PACKAGE_NAME, TestReceiver.class.getName()) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| result.set(getResultCode()); |
| final String resultStr = getResultData(); |
| apiResult.set(Boolean.parseBoolean(resultStr)); |
| resultLatch.countDown(); |
| } |
| }, null, Activity.RESULT_CANCELED, null, null); |
| // TODO (b/192043206): revoke app op for helper app once ag/15001282 is merged. |
| assertTrue("Timed out waiting for response from helper app", |
| resultLatch.await(10, TimeUnit.SECONDS)); |
| assertEquals(Activity.RESULT_OK, result.get()); |
| assertTrue("canScheduleExactAlarm returned false", apiResult.get()); |
| } |
| |
| @Test |
| public void canScheduleExactAlarmWithPolicyPermission() throws Exception { |
| final CountDownLatch resultLatch = new CountDownLatch(1); |
| final AtomicBoolean apiResult = new AtomicBoolean(false); |
| final AtomicInteger result = new AtomicInteger(-1); |
| |
| final Intent requestToTestApp = new Intent( |
| RequestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM) |
| .setClassName(RequestReceiver.PACKAGE_NAME, RequestReceiver.class.getName()) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| result.set(getResultCode()); |
| final String resultStr = getResultData(); |
| apiResult.set(Boolean.parseBoolean(resultStr)); |
| resultLatch.countDown(); |
| } |
| }, null, Activity.RESULT_CANCELED, null, null); |
| |
| assertTrue("Timed out waiting for response from helper app", |
| resultLatch.await(10, TimeUnit.SECONDS)); |
| assertEquals(Activity.RESULT_OK, result.get()); |
| assertTrue("canScheduleExactAlarm returned false", apiResult.get()); |
| } |
| |
| @Test |
| public void canScheduleExactAlarmWithPolicyPermissionSdk32() throws Exception { |
| final CountDownLatch resultLatch = new CountDownLatch(1); |
| final AtomicBoolean apiResult = new AtomicBoolean(true); |
| final AtomicInteger result = new AtomicInteger(-1); |
| |
| final Intent requestToTestApp = new Intent( |
| RequestReceiverSdk32.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM) |
| .setClassName(RequestReceiverSdk32.PACKAGE_NAME, |
| RequestReceiverSdk32.class.getName()) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| result.set(getResultCode()); |
| final String resultStr = getResultData(); |
| apiResult.set(Boolean.parseBoolean(resultStr)); |
| resultLatch.countDown(); |
| } |
| }, null, Activity.RESULT_CANCELED, null, null); |
| |
| assertTrue("Timed out waiting for response from helper app", |
| resultLatch.await(10, TimeUnit.SECONDS)); |
| assertEquals(Activity.RESULT_OK, result.get()); |
| assertFalse("canScheduleExactAlarm returned true", apiResult.get()); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void setAlarmClockWithoutPermission() throws IOException { |
| revokeAppOp(); |
| mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0, |
| false)); |
| } |
| |
| private void whitelistTestApp() { |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> mWhitelistManager.addToWhitelist(sContext.getOpPackageName())); |
| } |
| |
| @Test |
| public void setAlarmClockWithoutPermissionWithWhitelist() throws Exception { |
| revokeAppOp(); |
| whitelistTestApp(); |
| final long now = System.currentTimeMillis(); |
| final int numAlarms = 100; // Number much higher than any quota. |
| for (int i = 0; i < numAlarms; i++) { |
| final int id = mIdGenerator.nextInt(); |
| final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(now, |
| null); |
| mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false)); |
| assertTrue("Alarm " + id + " not received", |
| AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS)); |
| } |
| } |
| |
| @Test |
| public void setAlarmClockWithPermission() throws Exception { |
| final long now = System.currentTimeMillis(); |
| final int numAlarms = 100; // Number much higher than any quota. |
| for (int i = 0; i < numAlarms; i++) { |
| final int id = mIdGenerator.nextInt(); |
| final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(now, |
| null); |
| mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false)); |
| assertTrue("Alarm " + id + " not received", |
| AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS)); |
| } |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void setExactAwiWithoutPermissionOrWhitelist() throws IOException { |
| revokeAppOp(); |
| mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, 0, |
| getAlarmSender(0, false)); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void setExactPiWithoutPermissionOrWhitelist() throws IOException { |
| revokeAppOp(); |
| mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 0, getAlarmSender(0, false)); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void setExactCallbackWithoutPermissionOrWhitelist() throws IOException { |
| revokeAppOp(); |
| mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 0, "test", |
| new AlarmManager.OnAlarmListener() { |
| @Override |
| public void onAlarm() { |
| Log.e(TAG, "Alarm fired!"); |
| } |
| }, null); |
| } |
| |
| @Test |
| public void setExactAwiWithoutPermissionWithWhitelist() throws Exception { |
| revokeAppOp(); |
| whitelistTestApp(); |
| final long now = SystemClock.elapsedRealtime(); |
| // This is the user whitelist, so the app should get unrestricted alarms. |
| final int numAlarms = 100; // Number much higher than any quota. |
| for (int i = 0; i < numAlarms; i++) { |
| final int id = mIdGenerator.nextInt(); |
| mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now, |
| getAlarmSender(id, false)); |
| assertTrue("Alarm " + id + " not received", |
| AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS)); |
| } |
| } |
| |
| @Test |
| public void setExactAwiWithPermissionAndWhitelist() throws Exception { |
| whitelistTestApp(); |
| final long now = SystemClock.elapsedRealtime(); |
| // The user whitelist takes precedence, so the app should get unrestricted alarms. |
| final int numAlarms = 100; // Number much higher than any quota. |
| for (int i = 0; i < numAlarms; i++) { |
| final int id = mIdGenerator.nextInt(); |
| mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now, |
| getAlarmSender(id, false)); |
| assertTrue("Alarm " + id + " not received", |
| AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS)); |
| } |
| } |
| |
| private static void reclaimQuota(int quotaToReclaim) { |
| final long eligibleAt = getNextEligibleTime(quotaToReclaim); |
| long now; |
| while ((now = SystemClock.elapsedRealtime()) < eligibleAt) { |
| try { |
| Thread.sleep(eligibleAt - now); |
| } catch (InterruptedException e) { |
| Log.e(TAG, "Thread interrupted while reclaiming quota!", e); |
| } |
| } |
| } |
| |
| private static long getNextEligibleTime(int quotaToReclaim) { |
| long t = AlarmReceiver.getNthLastAlarmTime(ALLOW_WHILE_IDLE_QUOTA - quotaToReclaim + 1); |
| return t + ALLOW_WHILE_IDLE_WINDOW; |
| } |
| |
| @Test |
| @Ignore("Flaky on cuttlefish") // TODO (b/171306433): Fix and re-enable |
| public void setExactAwiWithPermissionWithoutWhitelist() throws Exception { |
| reclaimQuota(ALLOW_WHILE_IDLE_QUOTA); |
| |
| int alarmId; |
| for (int i = 0; i < ALLOW_WHILE_IDLE_QUOTA; i++) { |
| final long trigger = SystemClock.elapsedRealtime() + 500; |
| mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, trigger, |
| getAlarmSender(alarmId = mIdGenerator.nextInt(), true)); |
| Thread.sleep(500); |
| assertTrue("Alarm " + alarmId + " not received", |
| AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS)); |
| } |
| long now = SystemClock.elapsedRealtime(); |
| final long nextTrigger = getNextEligibleTime(1); |
| assertTrue("Not enough margin to test reliably", nextTrigger > now + 5000); |
| |
| mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now, |
| getAlarmSender(alarmId = mIdGenerator.nextInt(), true)); |
| assertFalse("Alarm received when no quota", AlarmReceiver.waitForAlarm(alarmId, 5000)); |
| |
| now = SystemClock.elapsedRealtime(); |
| if (now < nextTrigger) { |
| Thread.sleep(nextTrigger - now); |
| } |
| assertTrue("Alarm " + alarmId + " not received when back in quota", |
| AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS)); |
| } |
| |
| private static void assertTempWhitelistState(boolean whitelisted) { |
| final String selfUid = String.valueOf(Process.myUid()); |
| SystemUtil.runShellCommand("cmd deviceidle tempwhitelist", |
| output -> (output.contains(selfUid) == whitelisted)); |
| } |
| |
| @Test |
| public void alarmClockGrantsWhitelist() throws Exception { |
| // no device idle in auto |
| assumeFalse(FeatureUtil.isAutomotive()); |
| |
| final int id = mIdGenerator.nextInt(); |
| final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo( |
| System.currentTimeMillis() + 100, null); |
| mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false)); |
| Thread.sleep(100); |
| assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id, |
| DEFAULT_WAIT_FOR_SUCCESS)); |
| assertTempWhitelistState(true); |
| } |
| |
| @Test |
| public void exactAwiGrantsWhitelist() throws Exception { |
| // no device idle in auto |
| assumeFalse(FeatureUtil.isAutomotive()); |
| |
| reclaimQuota(1); |
| final int id = mIdGenerator.nextInt(); |
| mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| SystemClock.elapsedRealtime() + 100, getAlarmSender(id, true)); |
| Thread.sleep(100); |
| assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id, |
| DEFAULT_WAIT_FOR_SUCCESS)); |
| assertTempWhitelistState(true); |
| } |
| |
| @Test |
| public void activityToRequestPermissionExists() { |
| // TODO(b/188070398) Remove this when auto supports the ACTION_REQUEST_SCHEDULE_EXACT_ALARM |
| assumeFalse(FeatureUtil.isAutomotive()); |
| |
| final Intent request = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); |
| final PackageManager pm = sContext.getPackageManager(); |
| |
| assertNotNull("No activity found for " + Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, |
| pm.resolveActivity(request, 0)); |
| |
| request.setData(Uri.fromParts("package", sContext.getOpPackageName(), null)); |
| |
| assertNotNull("No app specific activity found for " |
| + Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, pm.resolveActivity(request, 0)); |
| } |
| |
| /** |
| * Check if a given UID is in the "can start FGS" allowlist. |
| */ |
| private boolean checkThisAppTempAllowListed(int uid) { |
| // The allowlist used internally is ActivityManagerService.mFgsStartTempAllowList. We |
| // don't use the device-idle allowlist directly. |
| |
| // Run "dumpsys activity processes", and remove everything until "mFgsStartTempAllowList:". |
| String output = ShellUtils.runShellCommand("dumpsys activity processes"); |
| output = output.replaceFirst("^.*? mFgsStartTempAllowList:$", ""); |
| |
| final String uidStr = UserHandle.formatUid(uid); |
| final String expected = "^\\s*" + uidStr + ":"; |
| for (String line : output.split("\n")) { |
| if (line.matches(expected)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void prepareTestAppForBroadcast() { |
| // Just send an explicit foreground broadcast to the test app to make sure |
| // the app is out of force-stop. |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> mPackageManager.setComponentEnabledSetting(mPermissionChangeReceiver, |
| PackageManager.COMPONENT_ENABLED_STATE_ENABLED, |
| PackageManager.DONT_KILL_APP)); |
| Log.d(TAG, "Un-force-stoppping the test app"); |
| Intent i = new Intent("android.app.action.cts.ACTION_PING"); |
| i.setComponent(mPermissionChangeReceiver); |
| i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| sContext.sendBroadcast(i); |
| } |
| |
| @Test |
| public void scheduleExactAlarmPermissionStateChangedSentAppOp() throws Exception { |
| // Revoke the permission, and remove it from the temp-allowlist. |
| prepareTestAppForBroadcast(); |
| Log.d(TAG, "Revoking the appop"); |
| revokeAppOp(TEST_APP_PACKAGE); |
| removeFromWhitelists(TEST_APP_PACKAGE); |
| |
| final int uid = getPackageUid(TEST_APP_PACKAGE); |
| TestUtils.waitUntil("Package still allowlisted", |
| () -> !checkThisAppTempAllowListed(uid)); |
| |
| Thread.sleep(1000); // Give the system a little time to settle down. |
| |
| final IntentFilter filter = new IntentFilter( |
| PermissionStateChangedReceiver.ACTION_FGS_START_RESULT); |
| final AtomicReference<String> resultHolder = new AtomicReference<>(); |
| final CountDownLatch latch = new CountDownLatch(1); |
| final BroadcastReceiver receiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Log.d(TAG, "Received response intent: " + intent); |
| resultHolder.set(intent.getStringExtra( |
| PermissionStateChangedReceiver.EXTRA_FGS_START_RESULT)); |
| latch.countDown(); |
| } |
| }; |
| sContext.registerReceiver(receiver, filter); |
| try { |
| Log.d(TAG, "Granting the appop"); |
| setAppOp(TEST_APP_PACKAGE, AppOpsManager.MODE_ALLOWED); |
| |
| assertTrue("Didn't receive response", |
| latch.await(30, TimeUnit.SECONDS)); |
| assertEquals("Failure message should be empty", "", resultHolder.get()); |
| } finally { |
| sContext.unregisterReceiver(receiver); |
| } |
| } |
| |
| @Test |
| public void scheduleExactAlarmPermissionStateChangedSentDenyList() throws Exception { |
| prepareTestAppForBroadcast(); |
| Log.d(TAG, "Putting in deny list"); |
| mDeviceConfigHelper.with("exact_alarm_deny_list", TEST_APP_PACKAGE) |
| .commitAndAwaitPropagation(); |
| removeFromWhitelists(TEST_APP_PACKAGE); |
| |
| |
| final int uid = getPackageUid(TEST_APP_PACKAGE); |
| TestUtils.waitUntil("Package still allowlisted", |
| () -> !checkThisAppTempAllowListed(uid)); |
| |
| final IntentFilter filter = new IntentFilter( |
| PermissionStateChangedReceiver.ACTION_FGS_START_RESULT); |
| final AtomicReference<String> resultHolder = new AtomicReference<>(); |
| final CountDownLatch latch = new CountDownLatch(1); |
| final BroadcastReceiver receiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Log.d(TAG, "Received response intent: " + intent); |
| resultHolder.set(intent.getStringExtra( |
| PermissionStateChangedReceiver.EXTRA_FGS_START_RESULT)); |
| latch.countDown(); |
| } |
| }; |
| sContext.registerReceiver(receiver, filter); |
| try { |
| Log.d(TAG, "Removing from deny list"); |
| mDeviceConfigHelper.without("exact_alarm_deny_list").commitAndAwaitPropagation(); |
| |
| assertTrue("Didn't receive response", |
| latch.await(30, TimeUnit.SECONDS)); |
| assertEquals("Failure message should be empty", "", resultHolder.get()); |
| } finally { |
| sContext.unregisterReceiver(receiver); |
| } |
| } |
| |
| /** |
| * Put this package itself in the deny list and then remove it. |
| * Make sure canScheduleExactAlarms() returns the right value at after each operation. |
| */ |
| @Test |
| public void denyListedChangesCanScheduleExactAlarms() throws Exception { |
| mDeviceConfigHelper.with("exact_alarm_deny_list", sContext.getOpPackageName()) |
| .commitAndAwaitPropagation(); |
| assertFalse(mAlarmManager.canScheduleExactAlarms()); |
| |
| mDeviceConfigHelper.without("exact_alarm_deny_list").commitAndAwaitPropagation(); |
| assertTrue(mAlarmManager.canScheduleExactAlarms()); |
| |
| } |
| } |