diff options
| author | 2022-04-07 07:35:28 +0000 | |
|---|---|---|
| committer | 2022-04-07 07:35:28 +0000 | |
| commit | 04f3a367050e1993c438ff2c82ebb2cce36a3301 (patch) | |
| tree | 048af5aa6f1e6dc790ad41f8fab6036cf0ecd226 | |
| parent | 6ccb8cb4058a45e1648532bbeb3b5cbbca95e880 (diff) | |
| parent | a1adc26c843bc3f0a05b0b07aba38db289b3e410 (diff) | |
Merge "Deny SCHEDULE_EXACT_ALARM by default to newer apps" into tm-dev
5 files changed, 230 insertions, 34 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 61424ae0e158..1b9cf2648a3f 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -27,6 +27,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -280,6 +281,18 @@ public class AlarmManager { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long ENABLE_USE_EXACT_ALARM = 218533173L; + /** + * For apps targeting {@link Build.VERSION_CODES#TIRAMISU} or above, the permission + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the user explicitly + * allows it from Settings. + * + * TODO (b/226439802): change to EnabledSince(T) after SDK finalization. + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) + public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L; + @UnsupportedAppUsage private final IAlarmManager mService; private final Context mContext; diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index f67e8d2baa11..881453fe237d 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -66,6 +66,7 @@ import android.app.IAlarmListener; import android.app.IAlarmManager; import android.app.PendingIntent; import android.app.compat.CompatChanges; +import android.app.role.RoleManager; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; @@ -164,6 +165,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TimeZone; @@ -224,6 +226,7 @@ public class AlarmManagerService extends SystemService { private ActivityManagerInternal mActivityManagerInternal; private final EconomyManagerInternal mEconomyManagerInternal; private PackageManagerInternal mPackageManagerInternal; + private RoleManager mRoleManager; private volatile PermissionManagerServiceInternal mLocalPermissionManager; final Object mLock = new Object(); @@ -562,6 +565,9 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = "kill_on_schedule_exact_alarm_revoked"; + @VisibleForTesting + static final String KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = + "schedule_exact_alarm_denied_by_default"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -606,6 +612,9 @@ public class AlarmManagerService extends SystemService { private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true; + // TODO(b/226439802): Flip to true. + private static final boolean DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = false; + // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -693,6 +702,14 @@ public class AlarmManagerService extends SystemService { public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED; + /** + * When this is {@code true}, apps with the change + * {@link AlarmManager#SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT} enabled will not get + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} unless the user grants it to them. + */ + public volatile boolean SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = + DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; + public boolean USE_TARE_POLICY = Settings.Global.DEFAULT_ENABLE_TARE == 1; private long mLastAllowWhileIdleWhitelistDuration = -1; @@ -876,6 +893,15 @@ public class AlarmManagerService extends SystemService { KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED, DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); break; + case KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT: + final boolean oldValue = SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; + + SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = properties.getBoolean( + KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, + DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT); + handleScheduleExactAlarmDeniedByDefaultChange(oldValue, + SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -946,6 +972,15 @@ public class AlarmManagerService extends SystemService { } } + private void handleScheduleExactAlarmDeniedByDefaultChange(boolean oldValue, + boolean newValue) { + if (oldValue == newValue) { + return; + } + mHandler.obtainMessage(AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE, + newValue).sendToTarget(); + } + private void migrateAlarmsToNewStoreLocked() { final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore() : new BatchingAlarmStore(); @@ -1122,6 +1157,9 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED, KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); pw.println(); + pw.print(KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, + SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT); + pw.println(); pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY); pw.println(); @@ -1892,11 +1930,10 @@ public class AlarmManagerService extends SystemService { if (hasUseExactAlarmInternal(packageName, uid)) { return; } - - final boolean requested = mExactAlarmCandidates.contains( - UserHandle.getAppId(uid)); - final boolean denyListed = - mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); + if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { + // Permission not requested, app op doesn't matter. + return; + } final int newMode = mAppOps.checkOpNoThrow( AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName); @@ -1913,11 +1950,24 @@ public class AlarmManagerService extends SystemService { mLastOpScheduleExactAlarm.setValueAt(index, newMode); } } - - final boolean hadPermission = getScheduleExactAlarmState(requested, - denyListed, oldMode); - final boolean hasPermission = getScheduleExactAlarmState(requested, - denyListed, newMode); + if (oldMode == newMode) { + return; + } + final boolean allowedByDefault = + isScheduleExactAlarmAllowedByDefault(packageName, uid); + + final boolean hadPermission; + if (oldMode != AppOpsManager.MODE_DEFAULT) { + hadPermission = (oldMode == AppOpsManager.MODE_ALLOWED); + } else { + hadPermission = allowedByDefault; + } + final boolean hasPermission; + if (newMode != AppOpsManager.MODE_DEFAULT) { + hasPermission = (newMode == AppOpsManager.MODE_ALLOWED); + } else { + hasPermission = allowedByDefault; + } if (hadPermission && !hasPermission) { mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS, @@ -1939,6 +1989,8 @@ public class AlarmManagerService extends SystemService { LocalServices.getService(AppStandbyInternal.class); appStandbyInternal.addListener(new AppStandbyTracker()); + mRoleManager = getContext().getSystemService(RoleManager.class); + mMetricsHelper.registerPuller(() -> mAlarmStore); } } @@ -2525,19 +2577,6 @@ public class AlarmManagerService extends SystemService { } } - private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed, - int appOpMode) { - // This does not account for the state of the USE_EXACT_ALARM permission. - // The caller should do that separately. - if (!requested) { - return false; - } - if (appOpMode == AppOpsManager.MODE_DEFAULT) { - return !denyListed; - } - return appOpMode == AppOpsManager.MODE_ALLOWED; - } - boolean hasUseExactAlarmInternal(String packageName, int uid) { return isUseExactAlarmEnabled(packageName, UserHandle.getUserId(uid)) && (PermissionChecker.checkPermissionForPreflight(getContext(), @@ -2545,6 +2584,32 @@ public class AlarmManagerService extends SystemService { packageName) == PermissionChecker.PERMISSION_GRANTED); } + /** + * Returns whether SCHEDULE_EXACT_ALARM is allowed by default. + */ + boolean isScheduleExactAlarmAllowedByDefault(String packageName, int uid) { + if (isScheduleExactAlarmDeniedByDefault(packageName, UserHandle.getUserId(uid))) { + + // This is essentially like changing the protection level of the permission to + // (privileged|signature|role|appop), but have to implement this logic to maintain + // compatibility for older apps. + if (mPackageManagerInternal.isPlatformSigned(packageName) + || mPackageManagerInternal.isUidPrivileged(uid)) { + return true; + } + final long token = Binder.clearCallingIdentity(); + try { + final List<String> wellbeingHolders = (mRoleManager != null) + ? mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING) + : Collections.emptyList(); + return wellbeingHolders.contains(packageName); + } finally { + Binder.restoreCallingIdentity(token); + } + } + return !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); + } + boolean hasScheduleExactAlarmInternal(String packageName, int uid) { final long start = mStatLogger.getTime(); @@ -2560,7 +2625,7 @@ public class AlarmManagerService extends SystemService { final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName); if (mode == AppOpsManager.MODE_DEFAULT) { - hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); + hasPermission = isScheduleExactAlarmAllowedByDefault(packageName, uid); } else { hasPermission = (mode == AppOpsManager.MODE_ALLOWED); } @@ -2860,6 +2925,13 @@ public class AlarmManagerService extends SystemService { packageName, UserHandle.of(userId)); } + private boolean isScheduleExactAlarmDeniedByDefault(String packageName, int userId) { + return mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT + && CompatChanges.isChangeEnabled( + AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, packageName, + UserHandle.of(userId)); + } + @NeverCompile // Avoid size overhead of debugging code. void dumpImpl(IndentingPrintWriter pw) { synchronized (mLock) { @@ -3769,26 +3841,27 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(changedPackage, userId)) { continue; } + if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) { + continue; + } if (hasUseExactAlarmInternal(changedPackage, uid)) { continue; } + if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { + // Permission isn't requested, deny list doesn't matter. + 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) { + if (appOpMode != AppOpsManager.MODE_DEFAULT) { + // Deny list doesn't matter. continue; } + // added: true => package was added to the deny list + // added: false => package was removed from the deny list if (added) { synchronized (mLock) { removeExactAlarmsOnPermissionRevokedLocked(uid, @@ -4634,6 +4707,7 @@ public class AlarmManagerService extends SystemService { public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; public static final int TARE_AFFORDABILITY_CHANGED = 12; public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; + public static final int CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE = 14; AlarmHandler() { super(Looper.myLooper()); @@ -4759,6 +4833,35 @@ public class AlarmManagerService extends SystemService { } } break; + case CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE: + final boolean defaultDenied = (Boolean) msg.obj; + + final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds(); + for (int appId : mExactAlarmCandidates) { + for (int userId : startedUserIds) { + uid = UserHandle.getUid(userId, appId); + + final AndroidPackage packageForUid = + mPackageManagerInternal.getPackage(uid); + if (packageForUid == null) { + continue; + } + final String pkg = packageForUid.getPackageName(); + if (defaultDenied) { + if (!hasScheduleExactAlarmInternal(pkg, uid) + && !hasUseExactAlarmInternal(pkg, uid)) { + synchronized (mLock) { + removeExactAlarmsOnPermissionRevokedLocked(uid, pkg, + true); + } + } + } else if (hasScheduleExactAlarmInternal(pkg, uid)) { + sendScheduleExactAlarmPermissionStateChangedBroadcast(pkg, + UserHandle.getUserId(uid)); + } + } + } + break; default: // nope, just ignore it break; diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index f34c5062144e..06f698efde2b 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1208,6 +1208,11 @@ public abstract class PackageManagerInternal { public abstract SharedUserApi getSharedUserApi(int sharedUserAppId); /** + * Returns if the given uid is privileged or not. + */ + public abstract boolean isUidPrivileged(int uid); + + /** * Initiates a package state mutation request, returning the current state as known by * PackageManager. This allows the later commit request to compare the initial values and * determine if any state was changed or any packages were updated since the whole request diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 2fe7913342a2..ec6443db9f0f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -722,6 +722,11 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { return snapshot().getSharedUser(sharedUserAppId); } + @Override + public boolean isUidPrivileged(int uid) { + return snapshot().isUidPrivileged(uid); + } + @NonNull @Override @Deprecated diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index c9523ec16b8f..529def3697cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.app.AlarmManager.FLAG_STANDALONE; import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; +import static android.app.AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; import static android.app.AlarmManager.WINDOW_EXACT; import static android.app.AlarmManager.WINDOW_HEURISTIC; import static android.app.AppOpsManager.MODE_ALLOWED; @@ -122,6 +123,7 @@ import android.app.IAlarmListener; import android.app.IAlarmManager; import android.app.PendingIntent; import android.app.compat.CompatChanges; +import android.app.role.RoleManager; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; @@ -184,6 +186,7 @@ import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -232,6 +235,8 @@ public class AlarmManagerServiceTest { @Mock private PackageManagerInternal mPackageManagerInternal; @Mock + private RoleManager mRoleManager; + @Mock private AppStateTrackerImpl mAppStateTracker; @Mock private AlarmManagerService.ClockReceiver mClockReceiver; @@ -457,6 +462,7 @@ public class AlarmManagerServiceTest { when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager); + when(mMockContext.getSystemService(RoleManager.class)).thenReturn(mRoleManager); registerAppIds(new String[]{TEST_CALLING_PACKAGE}, new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)}); @@ -3191,6 +3197,70 @@ public class AlarmManagerServiceTest { } @Test + public void isScheduleExactAlarmAllowedByDefault() { + final String package1 = "priv"; + final String package2 = "signed"; + final String package3 = "normal"; + final String package4 = "wellbeing"; + final int uid1 = 1294; + final int uid2 = 8321; + final int uid3 = 3412; + final int uid4 = 4591; + + when(mPackageManagerInternal.isUidPrivileged(uid1)).thenReturn(true); + when(mPackageManagerInternal.isUidPrivileged(uid2)).thenReturn(false); + when(mPackageManagerInternal.isUidPrivileged(uid3)).thenReturn(false); + when(mPackageManagerInternal.isUidPrivileged(uid4)).thenReturn(false); + + when(mPackageManagerInternal.isPlatformSigned(package1)).thenReturn(false); + when(mPackageManagerInternal.isPlatformSigned(package2)).thenReturn(true); + when(mPackageManagerInternal.isPlatformSigned(package3)).thenReturn(false); + when(mPackageManagerInternal.isPlatformSigned(package4)).thenReturn(false); + + when(mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)).thenReturn( + Arrays.asList(package4)); + + mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); + mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = false; + mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] { + package1, + package3, + }); + + // Deny listed packages will be false. + assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1)); + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2)); + assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3)); + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4)); + + mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false); + mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true; + mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] { + package1, + package3, + }); + + // Same as above, deny listed packages will be false. + assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1)); + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2)); + assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3)); + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4)); + + mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); + mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true; + mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] { + package1, + package3, + }); + + // Deny list doesn't matter now, only exemptions should be true. + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1)); + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2)); + assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3)); + assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4)); + } + + @Test public void alarmScheduledAtomPushed() { for (int i = 0; i < 10; i++) { final PendingIntent pi = getNewMockPendingIntent(); |