Handle exact alarm permission state changes

The permission SCHEDULE_EXACT_ALARM state changes at the following
boundaries:
 1. App-op: This gets toggled by the user via Settings.
 2. Deny-list: Packages can be added to the deny list via DeviceConfig
 APIs.
 3. Package changes: A package's manifest may changes when it gets
 updated.

In both cases 1 and 2, if alarm manager detects a permission change
from revoked to granted, it sends the
ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED broadcast to the
app.
If it detects a permission change from granted to revoked, it kills
all the processes within the hosting uid.
In all three cases, when the permssion changes from granted to revoked,
all the exact alarms scheduled by the relevant package are removed.
Package updates are treated differently as they require processes to
restart anyway. The broadcast is not needed in this case as there
are no alarms expected to have been lost by a previous revocation, and
apps can always use ACTION_MY_PACKAGE_REPLACED for post-update setup as
usual.

All this only applies to packages that have the change
REQUIRE_EXACT_ALARM_PERMISSION enabled.

Also changed canScheduleExactAlarms to return false if the change is not
enabled for the calling package.

Test: atest FrameworksMockingServicesTests:AlarmManagerServiceTest
atest CtsAlarmManagerTestCases

Bug: 179541791
Bug: 187206399
Change-Id: Icd68275701f2804be65b3a10a7dd81bbf6e2a0bb
This commit is contained in:
Suprabh Shukla
2021-06-04 18:49:51 -07:00
parent 466698f9af
commit c3f067c103
4 changed files with 514 additions and 164 deletions

View File

@ -136,6 +136,8 @@ import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
@ -215,9 +217,18 @@ public class AlarmManagerService extends SystemService {
final Object mLock = new Object();
/** Immutable set of app ids that have requested SCHEDULE_EXACT_ALARM permission.*/
/** Immutable set of app ids requesting {@link Manifest.permission#SCHEDULE_EXACT_ALARM} */
@VisibleForTesting
volatile Set<Integer> mExactAlarmCandidates = Collections.emptySet();
/**
* A map from uid to the last op-mode we have seen for
* {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}
*/
@VisibleForTesting
@GuardedBy("mLock")
SparseIntArray mLastOpScheduleExactAlarm = new SparseIntArray();
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
private long mNextWakeup;
@ -522,6 +533,9 @@ public class AlarmManagerService extends SystemService {
static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz";
@VisibleForTesting
static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz";
@VisibleForTesting
static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
"kill_on_schedule_exact_alarm_revoked";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@ -564,6 +578,8 @@ public class AlarmManagerService extends SystemService {
private static final long DEFAULT_MIN_DEVICE_IDLE_FUZZ = 2 * 60_000;
private static final long DEFAULT_MAX_DEVICE_IDLE_FUZZ = 15 * 60_000;
private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true;
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@ -644,6 +660,13 @@ public class AlarmManagerService extends SystemService {
*/
public long MAX_DEVICE_IDLE_FUZZ = DEFAULT_MAX_DEVICE_IDLE_FUZZ;
/**
* Whether or not to kill app when the permission
* {@link Manifest.permission#SCHEDULE_EXACT_ALARM} is revoked.
*/
public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED;
private long mLastAllowWhileIdleWhitelistDuration = -1;
private int mVersion = 0;
@ -816,6 +839,11 @@ public class AlarmManagerService extends SystemService {
deviceIdleFuzzBoundariesUpdated = true;
}
break;
case KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED:
KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = properties.getBoolean(
KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
break;
default:
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
// The quotas need to be updated in order, so we can't just rely
@ -830,17 +858,24 @@ public class AlarmManagerService extends SystemService {
}
private void updateExactAlarmDenyList(String[] newDenyList) {
final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList));
final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST);
final Set<String> added = new ArraySet<>(newDenyList);
added.removeAll(EXACT_ALARM_DENY_LIST);
removed.removeAll(newSet);
if (added.size() > 0) {
mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added)
.sendToTarget();
}
if (removed.size() > 0) {
mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed)
.sendToTarget();
}
if (newDenyList.length == 0) {
EXACT_ALARM_DENY_LIST = Collections.emptySet();
} else {
final Set<String> oldSet = EXACT_ALARM_DENY_LIST;
final Set<String> newlyAdded = new ArraySet<>(newDenyList);
EXACT_ALARM_DENY_LIST = Collections.unmodifiableSet(new ArraySet<>(newlyAdded));
newlyAdded.removeAll(oldSet);
if (newlyAdded.size() > 0) {
mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_CHANGED, newlyAdded)
.sendToTarget();
}
EXACT_ALARM_DENY_LIST = newSet;
}
}
@ -1007,6 +1042,20 @@ public class AlarmManagerService extends SystemService {
pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST);
pw.println();
pw.print(KEY_MIN_DEVICE_IDLE_FUZZ);
pw.print("=");
TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw);
pw.println();
pw.print(KEY_MAX_DEVICE_IDLE_FUZZ);
pw.print("=");
TimeUtils.formatDuration(MAX_DEVICE_IDLE_FUZZ, pw);
pw.println();
pw.print(KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
pw.println();
pw.decreaseIndent();
}
@ -1667,16 +1716,57 @@ public class AlarmManagerService extends SystemService {
void refreshExactAlarmCandidates() {
final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages(
Manifest.permission.SCHEDULE_EXACT_ALARM);
final Set<Integer> appIds = new ArraySet<>(candidates.length);
final Set<Integer> newAppIds = new ArraySet<>(candidates.length);
for (final String candidate : candidates) {
final int uid = mPackageManagerInternal.getPackageUid(candidate,
PackageManager.MATCH_ANY_USER, USER_SYSTEM);
if (uid > 0) {
appIds.add(UserHandle.getAppId(uid));
newAppIds.add(UserHandle.getAppId(uid));
}
}
final ArraySet<Integer> removed = new ArraySet<>(mExactAlarmCandidates);
removed.removeAll(newAppIds);
// This code is only called on package_added and boot. The set {removed} is only expected to
// be non-empty when a package was updated and it removed the permission from its manifest.
for (int i = 0; i < removed.size(); i++) {
final int removedAppId = removed.valueAt(i);
synchronized (mLock) {
Slog.i(TAG, "App id " + removedAppId + " lost SCHEDULE_EXACT_ALARM on update");
final Predicate<Alarm> whichAlarms = a -> {
if (UserHandle.getAppId(a.uid) != removedAppId || a.windowLength != 0) {
return false;
}
if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) {
return false;
}
return a.alarmClock != null || !isExemptFromExactAlarmPermission(a.uid);
};
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
}
}
// No need to lock. Assignment is always atomic.
mExactAlarmCandidates = Collections.unmodifiableSet(appIds);
mExactAlarmCandidates = Collections.unmodifiableSet(newAppIds);
}
@Override
public void onUserStarting(TargetUser user) {
super.onUserStarting(user);
final int userId = user.getUserIdentifier();
mHandler.post(() -> {
for (final int appId : mExactAlarmCandidates) {
final int uid = UserHandle.getUid(userId, appId);
final AndroidPackage androidPackage = mPackageManagerInternal.getPackage(uid);
// It will be null if it is not installed on the starting user.
if (androidPackage != null) {
final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM,
uid, androidPackage.getPackageName());
synchronized (mLock) {
mLastOpScheduleExactAlarm.put(uid, mode);
}
}
}
});
}
@Override
@ -1706,17 +1796,44 @@ public class AlarmManagerService extends SystemService {
@Override
public void opChanged(int op, int uid, String packageName)
throws RemoteException {
if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) {
final int userId = UserHandle.getUserId(uid);
if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM
|| !isExactAlarmChangeEnabled(packageName, userId)) {
return;
}
if (!hasScheduleExactAlarmInternal(packageName, uid)) {
final boolean requested = mExactAlarmCandidates.contains(
UserHandle.getAppId(uid));
final boolean denyListed =
mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
final int newMode = mAppOps.checkOpNoThrow(
AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName);
final int oldMode;
synchronized (mLock) {
final int index = mLastOpScheduleExactAlarm.indexOfKey(uid);
if (index < 0) {
oldMode = AppOpsManager.opToDefaultMode(
AppOpsManager.OP_SCHEDULE_EXACT_ALARM);
mLastOpScheduleExactAlarm.put(uid, newMode);
} else {
oldMode = mLastOpScheduleExactAlarm.valueAt(index);
mLastOpScheduleExactAlarm.setValueAt(index, newMode);
}
}
final boolean hadPermission = getScheduleExactAlarmState(requested,
denyListed, oldMode);
final boolean hasPermission = getScheduleExactAlarmState(requested,
denyListed, newMode);
if (hadPermission && !hasPermission) {
mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
uid, 0, packageName).sendToTarget();
} else {
// TODO(b/187206399) Make sure this won't be sent, if the app
// already had the appop previously.
} else if (!hadPermission && hasPermission) {
sendScheduleExactAlarmPermissionStateChangedBroadcast(
packageName, UserHandle.getUserId(uid));
packageName, userId);
}
}
});
@ -2256,12 +2373,28 @@ public class AlarmManagerService extends SystemService {
}
}
private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed,
int appOpMode) {
if (!requested) {
return false;
}
if (appOpMode == AppOpsManager.MODE_DEFAULT) {
return !denyListed;
}
return appOpMode == AppOpsManager.MODE_ALLOWED;
}
boolean hasScheduleExactAlarmInternal(String packageName, int uid) {
// Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService.
// Not using #mLastOpScheduleExactAlarm as it may contain stale values.
// No locking needed as all internal containers being queried are immutable.
final long start = mStatLogger.getTime();
final boolean hasPermission;
// No locking needed as all internal containers being queried are immutable.
if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) {
hasPermission = false;
} else if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
hasPermission = false;
} else {
final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
packageName);
@ -2368,8 +2501,7 @@ public class AlarmManagerService extends SystemService {
} else if (exact || allowWhileIdle) {
final boolean needsPermission;
boolean lowerQuota;
if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
callingPackage, UserHandle.of(callingUserId))) {
if (isExactAlarmChangeEnabled(callingPackage, callingUserId)) {
needsPermission = exact;
lowerQuota = !exact;
idleOptions = exact ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle();
@ -2524,6 +2656,11 @@ public class AlarmManagerService extends SystemService {
}
};
private static boolean isExactAlarmChangeEnabled(String packageName, int userId) {
return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
packageName, UserHandle.of(userId));
}
void dumpImpl(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
@ -2672,6 +2809,17 @@ public class AlarmManagerService extends SystemService {
pw.println();
pw.println("App ids requesting SCHEDULE_EXACT_ALARM: " + mExactAlarmCandidates);
pw.println();
pw.print("Last OP_SCHEDULE_EXACT_ALARM: [");
for (int i = 0; i < mLastOpScheduleExactAlarm.size(); i++) {
if (i > 0) {
pw.print(", ");
}
UserHandle.formatUid(pw, mLastOpScheduleExactAlarm.keyAt(i));
pw.print(":" + AppOpsManager.modeToName(mLastOpScheduleExactAlarm.valueAt(i)));
}
pw.println("]");
pw.println();
pw.println("Next alarm clock information: ");
pw.increaseIndent();
@ -3362,30 +3510,58 @@ public class AlarmManagerService extends SystemService {
}
/**
* Called when some packages are added to the {@link Constants#EXACT_ALARM_DENY_LIST}, as this
* may cause some of them to lose their permission.
* Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that
* either got added or deleted.
* These packages may lose or gain the SCHEDULE_EXACT_ALARM permission.
*
* Note that these packages don't need to be installed on the device, but if they do have an
* exact alarm scheduled and they lose the permission, this alarm will be canceled.
* Note that these packages don't need to be installed on the device, but if they are and they
* do undergo a permission change, we will handle them appropriately.
*
* This should not be called with the lock held as it calls out to other services.
* This is not expected to get called frequently.
*/
void handlePackagesAddedToExactAlarmsDenyListLocked(ArraySet<String> packageNames) {
Slog.w(TAG, "Packages " + packageNames + " added to the exact alarm deny list.");
final Predicate<Alarm> whichAlarms = a -> {
if (!packageNames.contains(a.packageName) || a.windowLength != 0) {
return false;
void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) {
Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from")
+ " the exact alarm deny list.");
final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds();
for (int i = 0; i < changedPackages.size(); i++) {
final String changedPackage = changedPackages.valueAt(i);
for (final int userId : startedUserIds) {
final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId);
if (uid <= 0) {
continue;
}
if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
continue;
}
final int appOpMode;
synchronized (mLock) {
appOpMode = mLastOpScheduleExactAlarm.get(uid,
AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM));
}
final boolean requested = mExactAlarmCandidates.contains(UserHandle.getAppId(uid));
// added: true => package was added to the deny list
// added: false => package was removed from the deny list
final boolean hadPermission = getScheduleExactAlarmState(requested, !added,
appOpMode);
final boolean hasPermission = getScheduleExactAlarmState(requested, added,
appOpMode);
if (hadPermission == hasPermission) {
continue;
}
if (added) {
synchronized (mLock) {
removeExactAlarmsOnPermissionRevokedLocked(uid, changedPackage);
}
} else {
sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
}
}
if (!CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
a.packageName, UserHandle.getUserHandleForUid(a.uid))) {
return false;
}
if (a.alarmClock == null && isExemptFromExactAlarmPermission(a.uid)) {
return false;
}
return !hasScheduleExactAlarmInternal(a.packageName, a.uid);
};
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
}
}
/**
@ -3396,9 +3572,7 @@ public class AlarmManagerService extends SystemService {
*/
void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!");
if (!CompatChanges.isChangeEnabled(
AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
packageName, UserHandle.getUserHandleForUid(uid))) {
if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
return;
}
@ -3409,6 +3583,11 @@ public class AlarmManagerService extends SystemService {
return false;
};
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
"schedule_exact_alarm revoked");
}
}
private void removeAlarmsInternalLocked(Predicate<Alarm> whichAlarms, int reason) {
@ -3535,6 +3714,11 @@ public class AlarmManagerService extends SystemService {
mRemovalHistory.removeAt(i);
}
}
for (int i = mLastOpScheduleExactAlarm.size() - 1; i >= 0; i--) {
if (UserHandle.getUserId(mLastOpScheduleExactAlarm.keyAt(i)) == userHandle) {
mLastOpScheduleExactAlarm.removeAt(i);
}
}
}
void interactiveStateChangedLocked(boolean interactive) {
@ -4091,8 +4275,9 @@ public class AlarmManagerService extends SystemService {
public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_CANCELED = 7;
public static final int REMOVE_EXACT_ALARMS = 8;
public static final int EXACT_ALARM_DENY_LIST_CHANGED = 9;
public static final int REFRESH_EXACT_ALARM_CANDIDATES = 10;
public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9;
public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
AlarmHandler() {
super(Looper.myLooper());
@ -4179,10 +4364,11 @@ public class AlarmManagerService extends SystemService {
removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
}
break;
case EXACT_ALARM_DENY_LIST_CHANGED:
synchronized (mLock) {
handlePackagesAddedToExactAlarmsDenyListLocked((ArraySet<String>) msg.obj);
}
case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true);
break;
case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED:
handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false);
break;
case REFRESH_EXACT_ALARM_CANDIDATES:
refreshExactAlarmCandidates();
@ -4349,6 +4535,7 @@ public class AlarmManagerService extends SystemService {
case Intent.ACTION_UID_REMOVED:
mLastPriorityAlarmDispatch.delete(uid);
mRemovalHistory.delete(uid);
mLastOpScheduleExactAlarm.delete(uid);
return;
case Intent.ACTION_PACKAGE_REMOVED:
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {

View File

@ -254,6 +254,9 @@ public abstract class ActivityManagerInternal {
/** Returns the current user id. */
public abstract int getCurrentUserId();
/** Returns the currently started user ids. */
public abstract int[] getStartedUserIds();
/** Returns true if the user is running. */
public abstract boolean isUserRunning(@UserIdInt int userId, int flags);

View File

@ -15348,6 +15348,11 @@ public class ActivityManagerService extends IActivityManager.Stub
return mPendingIntentController.getPendingIntentFlags(target);
}
@Override
public int[] getStartedUserIds() {
return mUserController.getStartedUserArray();
}
@Override
public void setPendingIntentAllowBgActivityStarts(IIntentSender target,
IBinder allowlistToken, int flags) {

View File

@ -58,7 +58,8 @@ import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION;
import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
@ -103,10 +104,8 @@ import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@ -155,6 +154,8 @@ import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.usage.AppStandbyInternal;
@ -175,6 +176,7 @@ import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@ -370,8 +372,9 @@ public class AlarmManagerServiceTest {
.mockStatic(LocalServices.class)
.spyStatic(Looper.class)
.mockStatic(MetricsHelper.class)
.mockStatic(Settings.Global.class)
.mockStatic(PermissionManagerService.class)
.mockStatic(ServiceManager.class)
.mockStatic(Settings.Global.class)
.mockStatic(SystemProperties.class)
.spyStatic(UserHandle.class)
.strictness(Strictness.WARN)
@ -394,7 +397,7 @@ public class AlarmManagerServiceTest {
doCallRealMethod().when((MockedVoidMethod) () ->
LocalServices.addService(eq(AlarmManagerInternal.class), any()));
doCallRealMethod().when(() -> LocalServices.getService(AlarmManagerInternal.class));
doReturn(false).when(() -> UserHandle.isCore(TEST_CALLING_UID));
doReturn(false).when(() -> UserHandle.isCore(anyInt()));
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE);
doReturn(Looper.getMainLooper()).when(Looper::myLooper);
@ -983,8 +986,7 @@ public class AlarmManagerServiceTest {
verify(mService.mHandler, atLeastOnce()).sendMessageAtTime(messageCaptor.capture(),
anyLong());
final Message lastMessage = messageCaptor.getValue();
assertEquals("Unexpected message send to handler", lastMessage.what,
what);
assertEquals("Unexpected message send to handler", what, lastMessage.what);
mService.mHandler.handleMessage(lastMessage);
}
@ -1876,6 +1878,8 @@ public class AlarmManagerServiceTest {
@Test
public void hasScheduleExactAlarmBinderCallNotDenyListed() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT);
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
@ -1891,6 +1895,8 @@ public class AlarmManagerServiceTest {
@Test
public void hasScheduleExactAlarmBinderCallDenyListed() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, true, MODE_ERRORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
@ -1904,8 +1910,26 @@ public class AlarmManagerServiceTest {
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
@Test
public void hasScheduleExactAlarmBinderCallChangeDisabled() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
mockExactAlarmPermissionGrant(true, true, MODE_ALLOWED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
private void mockChangeEnabled(long changeId, boolean enabled) {
doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
any(UserHandle.class)));
}
@Test
public void hasScheduleExactAlarmBinderCallNotDeclared() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(false, false, MODE_DEFAULT);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
@ -1918,9 +1942,7 @@ public class AlarmManagerServiceTest {
@Test
public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
// alarm clock
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
@ -1946,9 +1968,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
@ -1963,9 +1983,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
@ -1985,9 +2003,7 @@ public class AlarmManagerServiceTest {
@Test
public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
@ -2006,9 +2022,7 @@ public class AlarmManagerServiceTest {
@Test
public void alarmClockBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
final PendingIntent alarmPi = getNewMockPendingIntent();
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
@ -2023,9 +2037,7 @@ public class AlarmManagerServiceTest {
@Test
public void alarmClockBinderCall() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
@ -2060,7 +2072,6 @@ public class AlarmManagerServiceTest {
} else {
setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
}
when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(mode);
}
@ -2068,9 +2079,7 @@ public class AlarmManagerServiceTest {
@Test
public void alarmClockBinderCallWithoutPermission() throws RemoteException {
setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true);
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
@ -2089,9 +2098,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactBinderCallWithPermission() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
final PendingIntent alarmPi = getNewMockPendingIntent();
@ -2115,9 +2122,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactBinderCallWithAllowlist() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
@ -2137,9 +2142,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
final PendingIntent alarmPi = getNewMockPendingIntent();
@ -2162,9 +2165,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
@ -2191,9 +2192,7 @@ public class AlarmManagerServiceTest {
@Test
public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true);
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
@ -2220,9 +2219,7 @@ public class AlarmManagerServiceTest {
public void inexactAllowWhileIdleBinderCall() throws RemoteException {
// Both permission and power exemption status don't matter for these alarms.
// We only want to test that the flags and idleOptions are correct.
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
@ -2244,9 +2241,7 @@ public class AlarmManagerServiceTest {
@Test
public void binderCallWithUserAllowlist() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
@ -2266,10 +2261,7 @@ public class AlarmManagerServiceTest {
@Test
public void minWindowChangeEnabled() {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(
eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true);
final int minWindow = 73;
setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
@ -2315,10 +2307,7 @@ public class AlarmManagerServiceTest {
@Test
public void minWindowChangeDisabled() {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(
eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, false);
final long minWindow = 73;
setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
@ -2335,10 +2324,7 @@ public class AlarmManagerServiceTest {
@Test
public void minWindowPriorityAlarm() {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(
eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true);
final long minWindow = 73;
setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
@ -2356,76 +2342,135 @@ public class AlarmManagerServiceTest {
}
@Test
public void denyListPackagesAdded() {
public void denyListChanged() {
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT);
setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
assertAndHandleMessageSync(EXACT_ALARM_DENY_LIST_CHANGED);
final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(),
anyLong());
final List<Message> messages = messageCaptor.getAllValues();
for (final Message msg : messages) {
assertTrue("Unwanted message sent to handler: " + msg.what,
msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED
|| msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED);
mService.mHandler.handleMessage(msg);
}
ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"});
verify(mService).handlePackagesAddedToExactAlarmsDenyListLocked(eq(added));
verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true));
ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"});
verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false));
}
@Test
public void denyListPackagesRemoved() {
clearInvocations(mService.mHandler);
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2");
verifyNoMoreInteractions(mService.mHandler);
public void permissionGrantedDueToDenyList() {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
final String[] packages = {"example.package.1", "example.package.2"};
final int appId1 = 232;
final int appId2 = 431;
final int userId1 = 42;
final int userId2 = 53;
registerAppIds(packages, new Integer[]{appId1, appId2});
when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
when(mPermissionManagerInternal.getAppOpPermissionPackages(
SCHEDULE_EXACT_ALARM)).thenReturn(packages);
mService.refreshExactAlarmCandidates();
final long allowListDuration = 53442;
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(
allowListDuration);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
// No permission revoked.
verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString());
// Permission got granted only for (appId1, userId2).
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class);
verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(),
isNull(), bundleCaptor.capture());
assertEquals(userId2, userCaptor.getValue().getIdentifier());
// Validate the intent.
assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
intentCaptor.getValue().getAction());
assertEquals(packages[0], intentCaptor.getValue().getPackage());
// Validate the options.
final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
bOptions.getTemporaryAppAllowlistType());
assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration());
assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
bOptions.getTemporaryAppAllowlistReasonCode());
}
@Test
public void removeExactAlarmsOnPackageAddedToDenyList() {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
public void permissionRevokedDueToDenyList() {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// basic exact alarm
setTestAlarm(ELAPSED_REALTIME, 1, 0, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID,
null);
// exact and allow-while-idle alarm
setTestAlarm(ELAPSED_REALTIME, 2, 0, getNewMockPendingIntent(), 0, FLAG_ALLOW_WHILE_IDLE,
TEST_CALLING_UID, null);
// alarm clock
setWakeFromIdle(RTC_WAKEUP, 3, getNewMockPendingIntent());
final String[] packages = {"example.package.1", "example.package.2"};
final PendingIntent inexact = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME, 4, 10, inexact, 0, 0, TEST_CALLING_UID, null);
final int appId1 = 232;
final int appId2 = 431;
final PendingIntent inexactAwi = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME, 5, 10, inexactAwi, 0, FLAG_ALLOW_WHILE_IDLE,
TEST_CALLING_UID, null);
final int userId1 = 42;
final int userId2 = 53;
final String differentPackage = "different.package";
final PendingIntent exactButDifferentPackage = getNewMockPendingIntent(
TEST_CALLING_UID, differentPackage);
setTestAlarm(ELAPSED_REALTIME, 6, 0, exactButDifferentPackage, 0, 0,
TEST_CALLING_UID, differentPackage, null);
assertEquals(6, mService.mAlarmStore.size());
registerAppIds(packages, new Integer[]{appId1, appId2});
when(mAppOpsManager.checkOpNoThrow(eq(OP_SCHEDULE_EXACT_ALARM), anyInt(),
anyString())).thenReturn(MODE_DEFAULT);
setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE);
assertAndHandleMessageSync(EXACT_ALARM_DENY_LIST_CHANGED);
verify(mService).handlePackagesAddedToExactAlarmsDenyListLocked(
argThat(set -> (set.size() == 1 && set.contains(TEST_CALLING_PACKAGE))));
when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
final ArrayList<Alarm> remaining = mService.mAlarmStore.asList();
assertEquals(3, remaining.size());
assertTrue("Basic inexact alarm removed",
remaining.removeIf(a -> a.matches(inexact, null)));
assertTrue("Inexact allow-while-idle alarm removed",
remaining.removeIf(a -> a.matches(inexactAwi, null)));
assertTrue("Alarm from different package removed",
remaining.removeIf(a -> a.matches(exactButDifferentPackage, null)));
when(mPermissionManagerInternal.getAppOpPermissionPackages(
SCHEDULE_EXACT_ALARM)).thenReturn(packages);
mService.refreshExactAlarmCandidates();
// Mock should return false by default.
verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist(
UserHandle.getAppId(TEST_CALLING_UID));
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true);
// Permission got revoked only for (appId1, userId2)
verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]));
verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]));
verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]));
verify(mService).removeExactAlarmsOnPermissionRevokedLocked(
eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]));
}
@Test
public void opScheduleExactAlarmRevoked() throws Exception {
public void opChangedPermissionRevoked() throws Exception {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID,
@ -2433,10 +2478,39 @@ public class AlarmManagerServiceTest {
}
@Test
public void opScheduleExactAlarmGranted() throws Exception {
public void opChangedChangeDisabled() throws Exception {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
verify(mService.mHandler, never()).sendMessageAtTime(
argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
}
@Test
public void opChangedNoPermissionChangeDueToDenyList() throws Exception {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
mockExactAlarmPermissionGrant(true, true, MODE_DEFAULT);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
verify(mService.mHandler, never()).sendMessageAtTime(
argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
}
@Test
public void opChangedPermissionGranted() throws Exception {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
final long durationMs = 20000L;
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
@ -2462,9 +2536,7 @@ public class AlarmManagerServiceTest {
@Test
public void removeExactAlarmsOnPermissionRevoked() {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// basic exact alarm
setTestAlarm(ELAPSED_REALTIME, 0, 0, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID,
@ -2501,6 +2573,9 @@ public class AlarmManagerServiceTest {
// Mock should return false by default.
verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist(
UserHandle.getAppId(TEST_CALLING_UID));
verify(() -> PermissionManagerService.killUid(eq(TEST_CALLING_UID), eq(TEST_CALLING_USER),
anyString()));
}
@Test
@ -2566,6 +2641,86 @@ public class AlarmManagerServiceTest {
});
}
@Test
public void onLastOpScheduleExactAlarmOnUserStart() {
final int userId = 54;
SystemService.TargetUser mockTargetUser = mock(SystemService.TargetUser.class);
when(mockTargetUser.getUserIdentifier()).thenReturn(userId);
final Integer[] appIds = new Integer[]{43, 254, 7731};
final int unknownAppId = 2347;
final String[] packageNames = new String[]{"p43", "p254", "p7731"};
final int[] appOpModes = new int[]{MODE_ALLOWED, MODE_IGNORED, MODE_ERRORED};
mService.mExactAlarmCandidates = new ArraySet<>(appIds);
mService.mExactAlarmCandidates.add(unknownAppId);
for (int i = 0; i < appIds.length; i++) {
final int uid = UserHandle.getUid(userId, appIds[i]);
final AndroidPackage pkg = mock(AndroidPackage.class);
when(pkg.getPackageName()).thenReturn(packageNames[i]);
when(mPackageManagerInternal.getPackage(uid)).thenReturn(pkg);
when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, uid,
packageNames[i])).thenReturn(appOpModes[i]);
}
final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
doReturn(true).when(mService.mHandler).post(runnableCaptor.capture());
mService.onUserStarting(mockTargetUser);
runnableCaptor.getValue().run();
assertEquals(appIds.length, mService.mLastOpScheduleExactAlarm.size());
for (int i = 0; i < appIds.length; i++) {
final int uid = UserHandle.getUid(userId, appIds[i]);
assertEquals(appOpModes[i], mService.mLastOpScheduleExactAlarm.get(uid, -1));
}
assertTrue(mService.mLastOpScheduleExactAlarm.indexOfKey(
UserHandle.getUid(userId, unknownAppId)) < 0);
}
@Test
public void refreshExactAlarmCandidatesRemovesExactAlarmsIfNeeded() {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mService.mExactAlarmCandidates = new ArraySet<>(new Integer[]{1, 2, 5});
final String[] updatedRequesters = new String[]{"p11", "p2", "p9"};
final Integer[] appIds = new Integer[]{11, 2, 9};
registerAppIds(updatedRequesters, appIds);
when(mPermissionManagerInternal.getAppOpPermissionPackages(
SCHEDULE_EXACT_ALARM)).thenReturn(updatedRequesters);
final PendingIntent exactAppId1 = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME, 0, 0, exactAppId1, 0, 0,
UserHandle.getUid(TEST_CALLING_USER, 1), null);
final PendingIntent exactAppId2 = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME, 0, 0, exactAppId2, 0, 0,
UserHandle.getUid(TEST_CALLING_USER, 2), null);
final PendingIntent exactAppId5 = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME, 0, 0, exactAppId5, 0, 0,
UserHandle.getUid(TEST_CALLING_USER, 5), null);
final PendingIntent inexactAppId5 = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME, 0, 23, inexactAppId5, 0, 0,
UserHandle.getUid(TEST_CALLING_USER, 5), null);
assertEquals(4, mService.mAlarmStore.size());
mService.refreshExactAlarmCandidates();
// App ids 1 and 5 lost the permission, so there alarms should be removed.
final ArrayList<Alarm> remaining = mService.mAlarmStore.asList();
assertEquals(2, remaining.size());
assertTrue("Inexact alarm removed",
remaining.removeIf(a -> a.matches(inexactAppId5, null)));
assertTrue("Alarm from app id 2 removed",
remaining.removeIf(a -> a.matches(exactAppId2, null)));
}
@Test
public void refreshExactAlarmCandidatesOnPackageAdded() {
final String[] exactAlarmRequesters = new String[]{"p11", "p2", "p9"};