Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c312eeaa authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

All exact alarms now require SCHEDULE_EXACT_ALARM

Even exact alarms outside of idle require this permission. This is to
draw a clear boundary of the permission. If the app needs exact timing,
it should request this permission and then choose the most appropriate
API for its needs. Otherwise, it has to work with inexact alarms, which
now cannot have a window smaller than a set minimum, currently, ten
seconds.

Test: atest FrameworksMockingServicesTests:com.android.server.alarm
atest CtsAlarmManagerTestCases

Bug: 182226633
Change-Id: Iec953dbe8570f1ea6aaf81d864468ef1eb690de6
parent 9ce91b84
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -539,6 +539,11 @@ public class AlarmManager {
     * scheduled as exact.  Applications are strongly discouraged from using exact
     * alarms unnecessarily as they reduce the OS's ability to minimize battery use.
     *
     * <p>
     * Starting with {@link Build.VERSION_CODES#S}, apps require the
     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
     * API.
     *
     * @param type type of alarm.
     * @param triggerAtMillis time in milliseconds that the alarm should go
     *        off, using the appropriate clock (depending on the alarm type).
@@ -558,6 +563,7 @@ public class AlarmManager {
     * @see #RTC
     * @see #RTC_WAKEUP
     */
    @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
    public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
                null, null);
@@ -571,7 +577,13 @@ public class AlarmManager {
     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
     * invoked via the specified target Handler, or on the application's main looper
     * if {@code null} is passed as the {@code targetHandler} parameter.
     *
     * <p>
     * Starting with {@link Build.VERSION_CODES#S}, apps require the
     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
     * API.
     */
    @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
    public void setExact(@AlarmType int type, long triggerAtMillis, String tag,
            OnAlarmListener listener, Handler targetHandler) {
        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+43 −38
Original line number Diff line number Diff line
@@ -388,6 +388,8 @@ public class AlarmManagerService extends SystemService {
        @VisibleForTesting
        static final String KEY_MAX_INTERVAL = "max_interval";
        @VisibleForTesting
        static final String KEY_MIN_WINDOW = "min_window";
        @VisibleForTesting
        static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                = "allow_while_idle_whitelist_duration";
        @VisibleForTesting
@@ -428,11 +430,13 @@ public class AlarmManagerService extends SystemService {
        @VisibleForTesting
        static final String KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW = "allow_while_idle_compat_window";

        private static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps";
        @VisibleForTesting
        static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps";

        private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
        private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
        private static final long DEFAULT_MAX_INTERVAL = 365 * INTERVAL_DAY;
        private static final long DEFAULT_MIN_WINDOW = 10_000;
        private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000;
        private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
        private static final int DEFAULT_MAX_ALARMS_PER_UID = 500;
@@ -475,6 +479,9 @@ public class AlarmManagerService extends SystemService {
        // Maximum alarm recurrence interval
        public long MAX_INTERVAL = DEFAULT_MAX_INTERVAL;

        // Minimum window size for inexact alarms
        public long MIN_WINDOW = DEFAULT_MIN_WINDOW;

        // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
        public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
                = DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -575,6 +582,9 @@ public class AlarmManagerService extends SystemService {
                                ALLOW_WHILE_IDLE_QUOTA = 1;
                            }
                            break;
                        case KEY_MIN_WINDOW:
                            MIN_WINDOW = properties.getLong(KEY_MIN_WINDOW, DEFAULT_MIN_WINDOW);
                            break;
                        case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA:
                            ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt(
                                    KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA,
@@ -738,6 +748,11 @@ public class AlarmManagerService extends SystemService {
            TimeUtils.formatDuration(MAX_INTERVAL, pw);
            pw.println();

            pw.print(KEY_MIN_WINDOW);
            pw.print("=");
            TimeUtils.formatDuration(MIN_WINDOW, pw);
            pw.println();

            pw.print(KEY_LISTENER_TIMEOUT);
            pw.print("=");
            TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
@@ -1642,6 +1657,7 @@ public class AlarmManagerService extends SystemService {
            // Fix this window in place, so that as time approaches we don't collapse it.
            windowLength = maxElapsed - triggerElapsed;
        } else {
            windowLength = Math.max(windowLength, mConstants.MIN_WINDOW);
            maxElapsed = triggerElapsed + windowLength;
        }
        synchronized (mLock) {
@@ -1981,8 +1997,10 @@ public class AlarmManagerService extends SystemService {
     * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact,
     * allow-while-idle alarms.
     */
    boolean isExemptFromPermission(int uid) {
        return (UserHandle.isSameApp(mSystemUiUid, uid) || mLocalDeviceIdleController == null
    boolean isExemptFromExactAlarmPermission(int uid) {
        return (UserHandle.isSameApp(mSystemUiUid, uid)
                || UserHandle.isCore(uid)
                || mLocalDeviceIdleController == null
                || mLocalDeviceIdleController.isAppOnWhitelist(UserHandle.getAppId(uid)));
    }

@@ -2002,54 +2020,43 @@ public class AlarmManagerService extends SystemService {
            mAppOps.checkPackage(callingUid, callingPackage);

            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
            final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT);

            Bundle idleOptions = null;
            if (alarmClock != null || allowWhileIdle) {
            // make sure the caller is allowed to use the requested kind of alarm, and also
                // decide what broadcast options to use.
            // decide what quota and broadcast options to use.
            Bundle idleOptions = null;
            if (exact || allowWhileIdle) {
                final boolean needsPermission;
                boolean lowQuota;
                boolean lowerQuota;
                if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
                        callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
                    if (windowLength != AlarmManager.WINDOW_EXACT) {
                        needsPermission = false;
                        lowQuota = true;
                        idleOptions = isExemptFromPermission(callingUid) ? mOptsWithFgs.toBundle()
                                : mOptsWithoutFgs.toBundle();
                    } else if (alarmClock != null) {
                        needsPermission = true;
                        lowQuota = false;
                        idleOptions = mOptsWithFgs.toBundle();
                    } else {
                        needsPermission = true;
                        lowQuota = false;
                        idleOptions = mOptsWithFgs.toBundle();
                    }
                    needsPermission = exact;
                    lowerQuota = !exact;
                    idleOptions = exact ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle();
                } else {
                    needsPermission = false;
                    lowQuota = allowWhileIdle;
                    lowerQuota = allowWhileIdle;
                    idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
                }
                if (needsPermission && !canScheduleExactAlarms()) {
                    if (alarmClock == null && isExemptFromPermission(callingUid)) {
                        // If the app is on the full system allow-list (not except-idle), we still
                        // allow the alarms, but with a lower quota to keep pre-S compatibility.
                        lowQuota = true;
                    } else {
                    if (alarmClock != null || !isExemptFromExactAlarmPermission(callingUid)) {
                        final String errorMessage = "Caller " + callingPackage + " needs to hold "
                                + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
                                + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock")
                                + " alarms.";
                                + "exact alarms.";
                        if (mConstants.CRASH_NON_CLOCK_APPS) {
                            throw new SecurityException(errorMessage);
                        } else {
                            Slog.wtf(TAG, errorMessage);
                            idleOptions = mOptsWithoutFgs.toBundle();
                            lowQuota = allowWhileIdle;
                        }
                    }
                    // If the app is on the full system power allow-list (not except-idle), or we're
                    // in a soft failure mode, we still allow the alarms.
                    // We give temporary allowlist to allow-while-idle alarms but without FGS
                    // capability. Note that apps that are in the power allow-list do not need it.
                    idleOptions = allowWhileIdle ? mOptsWithoutFgs.toBundle() : null;
                    lowerQuota = allowWhileIdle;
                }
                if (lowQuota) {
                if (lowerQuota) {
                    flags &= ~FLAG_ALLOW_WHILE_IDLE;
                    flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
                }
@@ -2998,13 +3005,10 @@ public class AlarmManagerService extends SystemService {
    /**
     * Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms
     * that the app is no longer eligible to use.
     * TODO (b/179541791): Revisit and write tests once UX is final.
     * TODO (b/179541791): Add revocation history to dumpsys.
     */
    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
        if (UserHandle.isCore(uid) || uid == mSystemUiUid) {
            return;
        }
        if (isExemptFromPermission(uid)) {
        if (isExemptFromExactAlarmPermission(uid)) {
            return;
        }
        if (!CompatChanges.isChangeEnabled(
@@ -3015,7 +3019,7 @@ public class AlarmManagerService extends SystemService {

        final Predicate<Alarm> whichAlarms =
                a -> (a.uid == uid && a.packageName.equals(packageName)
                        && ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null));
                        && a.windowLength == AlarmManager.WINDOW_EXACT);
        final ArrayList<Alarm> removed = mAlarmStore.remove(whichAlarms);
        final boolean didRemove = !removed.isEmpty();
        if (didRemove) {
@@ -3873,6 +3877,7 @@ public class AlarmManagerService extends SystemService {
        return alarm.creatorUid;
    }


    @VisibleForTesting
    class AlarmHandler extends Handler {
        public static final int ALARM_EVENT = 1;
+5 −0
Original line number Diff line number Diff line
@@ -133,6 +133,11 @@ public interface AlarmStore {
     */
    void dumpProto(ProtoOutputStream pos, long nowElapsed);

    /**
     * @return a name for this alarm store that can be used for debugging and tests.
     */
    String getName();

    /**
     * A functional interface used to update the alarm. Used to describe the update in
     * {@link #updateAlarmDeliveries(AlarmDeliveryCalculator)}
+9 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StatLogger;

import java.text.SimpleDateFormat;
@@ -40,6 +41,8 @@ import java.util.function.Predicate;
 * This keeps the alarms in batches, which are sorted on the start time of their delivery window.
 */
public class BatchingAlarmStore implements AlarmStore {
    @VisibleForTesting
    static final String TAG = BatchingAlarmStore.class.getSimpleName();

    private final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
    private int mSize;
@@ -49,7 +52,7 @@ public class BatchingAlarmStore implements AlarmStore {
        int REBATCH_ALL_ALARMS = 0;
    }

    final StatLogger mStatLogger = new StatLogger("BatchingAlarmStore stats", new String[]{
    final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{
            "REBATCH_ALL_ALARMS",
    });

@@ -211,6 +214,11 @@ public class BatchingAlarmStore implements AlarmStore {
        }
    }

    @Override
    public String getName() {
        return TAG;
    }

    private void insertAndBatchAlarm(Alarm alarm) {
        final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
                : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed());
+9 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StatLogger;

import java.text.SimpleDateFormat;
@@ -38,6 +39,8 @@ import java.util.function.Predicate;
 * This keeps the alarms in a sorted list, and only batches them at the time of delivery.
 */
public class LazyAlarmStore implements AlarmStore {
    @VisibleForTesting
    static final String TAG = LazyAlarmStore.class.getSimpleName();

    private final ArrayList<Alarm> mAlarms = new ArrayList<>();
    private Runnable mOnAlarmClockRemoved;
@@ -47,7 +50,7 @@ public class LazyAlarmStore implements AlarmStore {
        int GET_NEXT_WAKEUP_DELIVERY_TIME = 1;
    }

    final StatLogger mStatLogger = new StatLogger("LazyAlarmStore stats", new String[]{
    final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{
            "GET_NEXT_DELIVERY_TIME",
            "GET_NEXT_WAKEUP_DELIVERY_TIME",
    });
@@ -214,4 +217,9 @@ public class LazyAlarmStore implements AlarmStore {
            a.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARMS, nowElapsed);
        }
    }

    @Override
    public String getName() {
        return TAG;
    }
}
Loading