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

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

Adding a policy mechanism to control alarms

Alarms now keep track of their deliverability per different throttling
policies. Each policy can dictate a time such that the alarm cannot
expire any earlier than that. The ultimate delivery start is then
calculated as the latest of these times.

This makes it simpler to manage different throttling policies
separately.

Adding two policies to start:
1. Requester policy: This defines the time when the caller requested the
alarm to go off. For an alarm with FLAG_IDLE_UNTIL, this is adjusted to
be before the next WAKE_FROM_IDLE. This is an exceptional case as this
alarm is supposed to be exempt from all the other policies.
2. App-standby policy: This defines the time when the caller app is
going to be in quota as per its app-standby-bucket.

The delivery end is chosen to the delivery start or
(requester policy time + the supplied window), whichever is later.

Also, fixed some bugs related to idle-until-time not getting snapped
to the next wake-from-idle time in some cases.

Test: atest FrameworksMockingServicesTests:\
com.android.server.alarm
atest CtsAlarmManagerTestCases
atest CtsAppTestCases:AlarmManagerTest

Bug: 170279979
Change-Id: Ib7fac90fe4804b624568ca4c6eba7a4cbc8a8aad
parent 50c75788
Loading
Loading
Loading
Loading
+164 −61
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package com.android.server.alarm;

import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;

import static com.android.server.alarm.AlarmManagerService.clampPositive;

import android.app.AlarmManager;
import android.app.IAlarmListener;
import android.app.PendingIntent;
@@ -32,8 +35,28 @@ import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Class to describe an alarm that is used to the set the kernel timer that returns when the timer
 * expires. The timer will wake up the device if the alarm is a "wakeup" alarm.
 */
class Alarm {
    private static final int NUM_POLICIES = 2;
    /**
     * Index used to store the time the alarm was requested to expire. To be used with
     * {@link #setPolicyElapsed(int, long)}
     */
    public static final int REQUESTER_POLICY_INDEX = 0;
    /**
     * Index used to store the earliest time the alarm can expire based on app-standby policy.
     * To be used with {@link #setPolicyElapsed(int, long)}
     */
    public static final int APP_STANDBY_POLICY_INDEX = 1;

    public final int type;
    /**
     * The original trigger time supplied by the caller. This can be in the elapsed or rtc time base
     * depending on the type of this alarm
     */
    public final long origWhen;
    public final boolean wakeup;
    public final PendingIntent operation;
@@ -47,42 +70,40 @@ class Alarm {
    public final int creatorUid;
    public final String packageName;
    public final String sourcePackage;
    public final long windowLength;
    public final long repeatInterval;
    public int count;
    public long when;
    public long windowLength;
    public long whenElapsed;    // 'when' in the elapsed time base
    public long maxWhenElapsed; // also in the elapsed time base
    // Expected alarm expiry time before app standby deferring is applied.
    public long expectedWhenElapsed;
    public long expectedMaxWhenElapsed;
    public long repeatInterval;
    /** The earliest time this alarm is eligible to fire according to each policy */
    private long[] mPolicyWhenElapsed;
    /** The ultimate delivery time to be used for this alarm */
    private long mWhenElapsed;
    private long mMaxWhenElapsed;
    public AlarmManagerService.PriorityClass priorityClass;

    Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
            long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
            WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
            int _uid, String _pkgName) {
        type = _type;
        origWhen = _when;
        wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
                || _type == AlarmManager.RTC_WAKEUP;
        when = _when;
        whenElapsed = _whenElapsed;
        expectedWhenElapsed = _whenElapsed;
        windowLength = _windowLength;
        maxWhenElapsed = expectedMaxWhenElapsed = AlarmManagerService.clampPositive(_maxWhen);
        repeatInterval = _interval;
        operation = _op;
        listener = _rec;
        listenerTag = _listenerTag;
        statsTag = makeTag(_op, _listenerTag, _type);
        workSource = _ws;
        flags = _flags;
        alarmClock = _info;
        uid = _uid;
        packageName = _pkgName;
    Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
            PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
            AlarmManager.AlarmClockInfo info, int uid, String pkgName) {
        this.type = type;
        origWhen = when;
        wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
                || type == AlarmManager.RTC_WAKEUP;
        mPolicyWhenElapsed = new long[NUM_POLICIES];
        mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed;
        mWhenElapsed = requestedWhenElapsed;
        this.windowLength = windowLength;
        mMaxWhenElapsed = clampPositive(requestedWhenElapsed + windowLength);
        repeatInterval = interval;
        operation = op;
        listener = rec;
        this.listenerTag = listenerTag;
        statsTag = makeTag(op, listenerTag, type);
        workSource = ws;
        this.flags = flags;
        alarmClock = info;
        this.uid = uid;
        packageName = pkgName;
        sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
        creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
        creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
    }

    public static String makeTag(PendingIntent pi, String tag, int type) {
@@ -91,13 +112,6 @@ class Alarm {
        return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
    }

    public AlarmManagerService.WakeupEvent makeWakeupEvent(long nowRTC) {
        return new AlarmManagerService.WakeupEvent(nowRTC, creatorUid,
                (operation != null)
                    ? operation.getIntent().getAction()
                    : ("<listener>:" + listenerTag));
    }

    // Returns true if either matches
    public boolean matches(PendingIntent pi, IAlarmListener rec) {
        return (operation != null)
@@ -109,6 +123,65 @@ class Alarm {
        return packageName.equals(sourcePackage);
    }

    /**
     * Get the earliest time this alarm is allowed to expire based on the given policy.
     *
     * @param policyIndex The index of the policy. One of [{@link #REQUESTER_POLICY_INDEX},
     *                    {@link #APP_STANDBY_POLICY_INDEX}].
     */
    public long getPolicyElapsed(int policyIndex) {
        return mPolicyWhenElapsed[policyIndex];
    }

    /**
     * Get the earliest time that this alarm should be delivered to the requesting app.
     */
    public long getWhenElapsed() {
        return mWhenElapsed;
    }

    /**
     * Get the latest time that this alarm should be delivered to the requesting app. Will be equal
     * to {@link #getWhenElapsed()} in case this is an exact alarm.
     */
    public long getMaxWhenElapsed() {
        return mMaxWhenElapsed;
    }

    /**
     * Set the earliest time this alarm can expire based on the passed policy index.
     *
     * @return {@code true} if this change resulted in a change in the ultimate delivery time (or
     * time window in the case of inexact alarms) of this alarm.
     * @see #getWhenElapsed()
     * @see #getMaxWhenElapsed()
     * @see #getPolicyElapsed(int)
     */
    public boolean setPolicyElapsed(int policyIndex, long policyElapsed) {
        mPolicyWhenElapsed[policyIndex] = policyElapsed;
        return updateWhenElapsed();
    }

    /**
     * @return {@code true} if either {@link #mWhenElapsed} or {@link #mMaxWhenElapsed} changes
     * due to this call.
     */
    private boolean updateWhenElapsed() {
        final long oldWhenElapsed = mWhenElapsed;
        mWhenElapsed = 0;
        for (int i = 0; i < NUM_POLICIES; i++) {
            mWhenElapsed = Math.max(mWhenElapsed, mPolicyWhenElapsed[i]);
        }

        final long oldMaxWhenElapsed = mMaxWhenElapsed;
        // windowLength should always be >= 0 here.
        final long maxRequestedElapsed = clampPositive(
                mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] + windowLength);
        mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed);

        return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(128);
@@ -116,11 +189,11 @@ class Alarm {
        sb.append(Integer.toHexString(System.identityHashCode(this)));
        sb.append(" type ");
        sb.append(type);
        sb.append(" when ");
        sb.append(when);
        sb.append(" origWhen ");
        sb.append(origWhen);
        sb.append(" ");
        sb.append(" whenElapsed ");
        sb.append(whenElapsed);
        sb.append(getWhenElapsed());
        sb.append(" ");
        sb.append(sourcePackage);
        sb.append('}');
@@ -136,29 +209,45 @@ class Alarm {
        dump(ipw, nowELAPSED, sdf);
    }

    private static String policyIndexToString(int index) {
        switch (index) {
            case REQUESTER_POLICY_INDEX:
                return "requester";
            case APP_STANDBY_POLICY_INDEX:
                return "app_standby";
            default:
                return "unknown";
        }
    }

    public static String typeToString(int type) {
        switch (type) {
            case RTC:
                return "RTC";
            case RTC_WAKEUP:
                return "RTC_WAKEUP";
            case ELAPSED_REALTIME:
                return "ELAPSED";
            case ELAPSED_REALTIME_WAKEUP:
                return "ELAPSED_WAKEUP";
            default:
                return "--unknown--";
        }
    }

    public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) {
        final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
        ipw.print("tag=");
        ipw.println(statsTag);

        ipw.print("type=");
        ipw.print(type);
        ipw.print(" expectedWhenElapsed=");
        TimeUtils.formatDuration(expectedWhenElapsed, nowELAPSED, ipw);
        ipw.print(" expectedMaxWhenElapsed=");
        TimeUtils.formatDuration(expectedMaxWhenElapsed, nowELAPSED, ipw);
        ipw.print(" whenElapsed=");
        TimeUtils.formatDuration(whenElapsed, nowELAPSED, ipw);
        ipw.print(" maxWhenElapsed=");
        TimeUtils.formatDuration(maxWhenElapsed, nowELAPSED, ipw);
        ipw.print(" when=");
        ipw.print(typeToString(type));
        ipw.print(" origWhen=");
        if (isRtc) {
            ipw.print(sdf.format(new Date(when)));
            ipw.print(sdf.format(new Date(origWhen)));
        } else {
            TimeUtils.formatDuration(when, nowELAPSED, ipw);
            TimeUtils.formatDuration(origWhen, nowELAPSED, ipw);
        }
        ipw.println();

        ipw.print(" window=");
        TimeUtils.formatDuration(windowLength, ipw);
        ipw.print(" repeatInterval=");
@@ -168,6 +257,19 @@ class Alarm {
        ipw.print(" flags=0x");
        ipw.println(Integer.toHexString(flags));

        ipw.print("policyWhenElapsed:");
        for (int i = 0; i < NUM_POLICIES; i++) {
            ipw.print(" " + policyIndexToString(i) + "=");
            TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowELAPSED, ipw);
        }
        ipw.println();

        ipw.print("whenElapsed=");
        TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw);
        ipw.print(" maxWhenElapsed=");
        TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw);
        ipw.println();

        if (alarmClock != null) {
            ipw.println("Alarm clock:");

@@ -177,9 +279,10 @@ class Alarm {
            ipw.print("  showIntent=");
            ipw.println(alarmClock.getShowIntent());
        }
        if (operation != null) {
            ipw.print("operation=");
            ipw.println(operation);

        }
        if (listener != null) {
            ipw.print("listener=");
            ipw.println(listener.asBinder());
@@ -191,7 +294,7 @@ class Alarm {

        proto.write(AlarmProto.TAG, statsTag);
        proto.write(AlarmProto.TYPE, type);
        proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
        proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, getWhenElapsed() - nowElapsed);
        proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
        proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
        proto.write(AlarmProto.COUNT, count);
+119 −159

File changed.

Preview size limit exceeded, changes collapsed.

+14 −3
Original line number Diff line number Diff line
@@ -47,6 +47,15 @@ public interface AlarmStore {
     */
    ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms);

    /**
     * Gets the earliest alarm with the flag {@link android.app.AlarmManager#FLAG_WAKE_FROM_IDLE}
     * based on {@link Alarm#getWhenElapsed()}.
     *
     * @return An alarm object matching the description above or {@code null} if no such alarm was
     *         found.
     */
    Alarm getNextWakeFromIdleAlarm();

    /**
     * Returns the total number of alarms in this store.
     */
@@ -82,7 +91,7 @@ public interface AlarmStore {
     *
     * @return {@code true} if any of the alarm deliveries changed due to this call.
     */
    boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator);
    boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator);

    /**
     * Returns all the alarms in the form of a list.
@@ -97,6 +106,7 @@ public interface AlarmStore {
     * Primary useful for debugging. Can be called from the
     * {@link android.os.Binder#dump(FileDescriptor PrintWriter, String[]) dump} method of the
     * caller.
     *
     * @param ipw        The {@link IndentingPrintWriter} to write to.
     * @param nowElapsed the time when the dump is requested in the
     *                   {@link SystemClock#elapsedRealtime()
@@ -112,7 +122,7 @@ public interface AlarmStore {

    /**
     * A functional interface used to update the alarm. Used to describe the update in
     * {@link #recalculateAlarmDeliveries(AlarmDeliveryCalculator)}
     * {@link #updateAlarmDeliveries(AlarmDeliveryCalculator)}
     */
    @FunctionalInterface
    interface AlarmDeliveryCalculator {
@@ -125,3 +135,4 @@ public interface AlarmStore {
        boolean updateAlarmDelivery(Alarm a);
    }
}
+31 −14
Original line number Diff line number Diff line
@@ -66,8 +66,8 @@ public class BatchingAlarmStore implements AlarmStore {
    };

    private static final Comparator<Alarm> sIncreasingTimeOrder = (a1, a2) -> {
        long when1 = a1.whenElapsed;
        long when2 = a2.whenElapsed;
        long when1 = a1.getWhenElapsed();
        long when2 = a2.getWhenElapsed();
        if (when1 > when2) {
            return 1;
        }
@@ -99,11 +99,28 @@ public class BatchingAlarmStore implements AlarmStore {
        }
        if (!removed.isEmpty()) {
            mSize -= removed.size();
            // Not needed if only whole batches were removed, but keeping existing behavior.
            rebatchAllAlarms();
        }
        return removed;
    }

    @Override
    public Alarm getNextWakeFromIdleAlarm() {
        for (final Batch batch : mAlarmBatches) {
            if ((batch.mFlags & AlarmManager.FLAG_WAKE_FROM_IDLE) == 0) {
                continue;
            }
            for (int i = 0; i < batch.size(); i++) {
                final Alarm a = batch.get(i);
                if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
                    return a;
                }
            }
        }
        return null;
    }

    private void rebatchAllAlarms() {
        final long start = mStatLogger.getTime();
        final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone();
@@ -157,7 +174,7 @@ public class BatchingAlarmStore implements AlarmStore {
    }

    @Override
    public boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
    public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
        boolean changed = false;
        for (final Batch b : mAlarmBatches) {
            for (int i = 0; i < b.size(); i++) {
@@ -204,7 +221,7 @@ public class BatchingAlarmStore implements AlarmStore {

    private void insertAndBatchAlarm(Alarm alarm) {
        final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
                : attemptCoalesce(alarm.whenElapsed, alarm.maxWhenElapsed);
                : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed());

        if (whichBatch < 0) {
            addBatch(mAlarmBatches, new Batch(alarm));
@@ -247,8 +264,8 @@ public class BatchingAlarmStore implements AlarmStore {
        final ArrayList<Alarm> mAlarms = new ArrayList<>();

        Batch(Alarm seed) {
            mStart = seed.whenElapsed;
            mEnd = clampPositive(seed.maxWhenElapsed);
            mStart = seed.getWhenElapsed();
            mEnd = clampPositive(seed.getMaxWhenElapsed());
            mFlags = seed.flags;
            mAlarms.add(seed);
        }
@@ -276,12 +293,12 @@ public class BatchingAlarmStore implements AlarmStore {
            if (DEBUG_BATCH) {
                Slog.v(TAG, "Adding " + alarm + " to " + this);
            }
            if (alarm.whenElapsed > mStart) {
                mStart = alarm.whenElapsed;
            if (alarm.getWhenElapsed() > mStart) {
                mStart = alarm.getWhenElapsed();
                newStart = true;
            }
            if (alarm.maxWhenElapsed < mEnd) {
                mEnd = alarm.maxWhenElapsed;
            if (alarm.getMaxWhenElapsed() < mEnd) {
                mEnd = alarm.getMaxWhenElapsed();
            }
            mFlags |= alarm.flags;

@@ -309,11 +326,11 @@ public class BatchingAlarmStore implements AlarmStore {
                        Slog.wtf(TAG, "Removed TIME_TICK alarm");
                    }
                } else {
                    if (alarm.whenElapsed > newStart) {
                        newStart = alarm.whenElapsed;
                    if (alarm.getWhenElapsed() > newStart) {
                        newStart = alarm.getWhenElapsed();
                    }
                    if (alarm.maxWhenElapsed < newEnd) {
                        newEnd = alarm.maxWhenElapsed;
                    if (alarm.getMaxWhenElapsed() < newEnd) {
                        newEnd = alarm.getMaxWhenElapsed();
                    }
                    newFlags |= alarm.flags;
                    i++;
+143 −9
Original line number Diff line number Diff line
@@ -48,10 +48,13 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INT
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK;
import static com.android.server.alarm.AlarmManagerService.WORKING_INDEX;
import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
import static com.android.server.alarm.Constants.TEST_CALLING_UID;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -82,6 +85,7 @@ import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;

@@ -108,6 +112,8 @@ import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.Executor;
@@ -116,9 +122,7 @@ import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
public class AlarmManagerServiceTest {
    private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
    private static final String TEST_CALLING_PACKAGE = "com.android.framework.test-package";
    private static final int SYSTEM_UI_UID = 12345;
    private static final int TEST_CALLING_UID = 67890;
    private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);

    private long mAppStandbyWindow;
@@ -350,19 +354,31 @@ public class AlarmManagerServiceTest {
    }

    private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
        setTestAlarm(type, triggerTime, operation, 0, TEST_CALLING_UID);
        setTestAlarm(type, triggerTime, operation, 0, AlarmManager.FLAG_STANDALONE,
                TEST_CALLING_UID);
    }

    private void setRepeatingTestAlarm(int type, long firstTrigger, long interval,
            PendingIntent pi) {
        setTestAlarm(type, firstTrigger, pi, interval, TEST_CALLING_UID);
        setTestAlarm(type, firstTrigger, pi, interval, AlarmManager.FLAG_STANDALONE,
                TEST_CALLING_UID);
    }

    private void setIdleUntilAlarm(int type, long triggerTime, PendingIntent pi) {
        setTestAlarm(type, triggerTime, pi, 0, AlarmManager.FLAG_IDLE_UNTIL, TEST_CALLING_UID);
    }

    private void setWakeFromIdle(int type, long triggerTime, PendingIntent pi) {
        // Note: Only alarm clock alarms are allowed to include this flag in the actual service.
        // But this is a unit test so we'll only test the flag for granularity and convenience.
        setTestAlarm(type, triggerTime, pi, 0,
                AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE, TEST_CALLING_UID);
    }

    private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
            int callingUid) {
        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval,
                operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null,
                callingUid, TEST_CALLING_PACKAGE);
            int flags, int callingUid) {
        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval, operation, null,
                "test", flags, null, null, callingUid, TEST_CALLING_PACKAGE);
    }

    private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
@@ -1002,7 +1018,7 @@ public class AlarmManagerServiceTest {
        for (int i = 0; i < numAlarms; i++) {
            int mockUid = UserHandle.getUid(mockUserId, 1234 + i);
            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10,
                    getNewMockPendingIntent(mockUid), 0, mockUid);
                    getNewMockPendingIntent(mockUid), 0, AlarmManager.FLAG_STANDALONE, mockUid);
        }
        assertEquals(numAlarms, mService.mAlarmsPerUid.size());
        mService.removeUserLocked(mockUserId);
@@ -1142,6 +1158,116 @@ public class AlarmManagerServiceTest {
        }
    }

    @Test
    public void singleIdleUntil() {
        doReturn(0).when(mService).fuzzForDuration(anyLong());

        final PendingIntent idleUntilPi6 = getNewMockPendingIntent();
        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, idleUntilPi6);

        assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi6, null));
        assertEquals(mNowElapsedTest + 6, mTestTimer.getElapsed());
        assertEquals(mNowElapsedTest + 6, mService.mPendingIdleUntil.getWhenElapsed());

        final PendingIntent idleUntilPi2 = getNewMockPendingIntent();
        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, idleUntilPi2);

        // The same mPendingIdleUntil should get updated, even with a different PendingIntent.
        assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi2, null));
        assertEquals(mNowElapsedTest + 2, mTestTimer.getElapsed());
        assertEquals(1, mService.mAlarmStore.size());

        final PendingIntent idleUntilPi10 = getNewMockPendingIntent();
        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10, idleUntilPi10);

        // The same thing should happen even when the new alarm is in farther in the future.
        assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi10, null));
        assertEquals(mNowElapsedTest + 10, mTestTimer.getElapsed());
        assertEquals(1, mService.mAlarmStore.size());
    }

    @Test
    public void nextWakeFromIdle() throws Exception {
        assertNull(mService.mNextWakeFromIdle);

        final PendingIntent wakeFromIdle6 = getNewMockPendingIntent();
        final long trigger6 = mNowElapsedTest + 6;
        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger6, wakeFromIdle6);

        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
        assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
        assertEquals(trigger6, mTestTimer.getElapsed());

        final PendingIntent wakeFromIdle10 = getNewMockPendingIntent();
        final long trigger10 = mNowElapsedTest + 10;
        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger10, wakeFromIdle10);

        // mNextWakeFromIdle should not get updated.
        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
        assertEquals(trigger6, mTestTimer.getElapsed());
        assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());

        final PendingIntent wakeFromIdle3 = getNewMockPendingIntent();
        final long trigger3 = mNowElapsedTest + 3;
        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger3, wakeFromIdle3);

        // mNextWakeFromIdle should always reflect the next earliest wake_from_idle alarm.
        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle3, null));
        assertEquals(trigger3, mTestTimer.getElapsed());
        assertEquals(trigger3, mService.mNextWakeFromIdle.getWhenElapsed());

        mNowElapsedTest = trigger3;
        mTestTimer.expire();

        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
        assertEquals(trigger6, mTestTimer.getElapsed());
        assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());

        mService.removeLocked(wakeFromIdle6, null);

        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle10, null));
        assertEquals(trigger10, mTestTimer.getElapsed());
        assertEquals(trigger10, mService.mNextWakeFromIdle.getWhenElapsed());

        mService.removeLocked(wakeFromIdle10, null);
        assertNull(mService.mNextWakeFromIdle);
    }

    @Test
    public void idleUntilBeforeWakeFromIdle() {
        doReturn(0).when(mService).fuzzForDuration(anyLong());

        final PendingIntent idleUntilPi = getNewMockPendingIntent();
        final long requestedIdleUntil = mNowElapsedTest + 10;
        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, requestedIdleUntil, idleUntilPi);

        assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed());

        final PendingIntent wakeFromIdle5 = getNewMockPendingIntent();
        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, wakeFromIdle5);
        assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());

        final PendingIntent wakeFromIdle8 = getNewMockPendingIntent();
        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 8, wakeFromIdle8);
        assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());

        final PendingIntent wakeFromIdle12 = getNewMockPendingIntent();
        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 12, wakeFromIdle12);
        assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());

        mService.removeLocked(wakeFromIdle5, null);
        assertEquals(mNowElapsedTest + 8, mService.mPendingIdleUntil.getWhenElapsed());

        mService.removeLocked(wakeFromIdle8, null);
        assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed());

        mService.removeLocked(idleUntilPi, null);
        assertNull(mService.mPendingIdleUntil);

        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, idleUntilPi);
        assertEquals(mNowElapsedTest + 12, mService.mPendingIdleUntil.getWhenElapsed());
    }

    @After
    public void tearDown() {
        if (mMockingSession != null) {
@@ -1149,4 +1275,12 @@ public class AlarmManagerServiceTest {
        }
        LocalServices.removeServiceForTest(AlarmManagerInternal.class);
    }

    private void dumpAllAlarms(String tag, ArrayList<Alarm> alarms) {
        System.out.println(tag + ": ");
        IndentingPrintWriter ipw = new IndentingPrintWriter(new PrintWriter(System.out));
        AlarmManagerService.dumpAlarmList(ipw, alarms, mNowElapsedTest,
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
        ipw.close();
    }
}
Loading