1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.utils;
18 
19 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
20 
21 import android.annotation.ElapsedRealtimeLong;
22 import android.annotation.NonNull;
23 import android.annotation.UserIdInt;
24 import android.app.AlarmManager;
25 import android.content.Context;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.SystemClock;
29 import android.util.ArraySet;
30 import android.util.IndentingPrintWriter;
31 import android.util.Pair;
32 import android.util.Slog;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.util.Comparator;
38 import java.util.PriorityQueue;
39 import java.util.function.Predicate;
40 
41 /**
42  * An {@link AlarmManager.OnAlarmListener} that will queue up all pending alarms and only
43  * schedule one alarm for the earliest alarm. Since {@link AlarmManager} has a maximum limit on the
44  * number of alarms that can be set at one time, this allows clients to maintain alarm times for
45  * various keys without risking hitting the AlarmManager alarm limit. Only one alarm time will be
46  * kept for each key {@code K}.
47  *
48  * @param <K> Any class that will be used as the key. Must have a proper equals() implementation.
49  * @hide
50  */
51 public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener {
52     private static final String TAG = AlarmQueue.class.getSimpleName();
53     private static final boolean DEBUG = false;
54 
55     private static final long NOT_SCHEDULED = -1;
56     /**
57      * The threshold used to consider a new trigger time to be significantly different from the
58      * currently used trigger time.
59      */
60     private static final long SIGNIFICANT_TRIGGER_TIME_CHANGE_THRESHOLD_MS = MINUTE_IN_MILLIS;
61 
62     /**
63      * Internal priority queue for each key's alarm, ordered by the time the alarm should go off.
64      * The pair is the key and its associated alarm time (in the elapsed realtime timebase).
65      */
66     private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> {
67         private static final Comparator<Pair<?, Long>> sTimeComparator =
68                 (o1, o2) -> Long.compare(o1.second, o2.second);
69 
AlarmPriorityQueue()70         AlarmPriorityQueue() {
71             super(1, sTimeComparator);
72         }
73 
74         /**
75          * Remove any instances of the key from the queue.
76          *
77          * @return true if an instance was removed, false otherwise.
78          */
removeKey(@onNull Q key)79         public boolean removeKey(@NonNull Q key) {
80             boolean removed = false;
81             Pair[] alarms = toArray(new Pair[size()]);
82             for (int i = alarms.length - 1; i >= 0; --i) {
83                 if (key.equals(alarms[i].first)) {
84                     remove(alarms[i]);
85                     removed = true;
86                 }
87             }
88             return removed;
89         }
90     }
91 
92     @VisibleForTesting
93     static class Injector {
getElapsedRealtime()94         long getElapsedRealtime() {
95             return SystemClock.elapsedRealtime();
96         }
97     }
98 
99     /** Runnable used to schedule an alarm with AlarmManager. NEVER run this with the lock held. */
100     private final Runnable mScheduleAlarmRunnable = new Runnable() {
101         @Override
102         public void run() {
103             mHandler.removeCallbacks(this);
104 
105             final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
106             if (alarmManager == null) {
107                 // The system isn't fully booted. Clients of this class may not have
108                 // direct access to (be notified when) the system is ready, so retry
109                 // setting the alarm after some delay. Leave enough time so that we don't cause
110                 // any unneeded startup delay.
111                 mHandler.postDelayed(this, 30_000);
112                 return;
113             }
114             final long nextTriggerTimeElapsed;
115             final long minTimeBetweenAlarmsMs;
116             synchronized (mLock) {
117                 if (mTriggerTimeElapsed == NOT_SCHEDULED) {
118                     return;
119                 }
120                 nextTriggerTimeElapsed = mTriggerTimeElapsed;
121                 minTimeBetweenAlarmsMs = mMinTimeBetweenAlarmsMs;
122             }
123             // Never call out to AlarmManager with the lock held. This could sit below AM.
124             if (mExactAlarm) {
125                 alarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
126                         nextTriggerTimeElapsed, mAlarmTag, AlarmQueue.this, mHandler);
127             } else {
128                 alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME,
129                         nextTriggerTimeElapsed, minTimeBetweenAlarmsMs / 2,
130                         mAlarmTag, AlarmQueue.this, mHandler);
131             }
132         }
133     };
134 
135     private final Object mLock = new Object();
136 
137     private final Context mContext;
138     private final Handler mHandler;
139     private final Injector mInjector;
140 
141     @GuardedBy("mLock")
142     private final AlarmPriorityQueue<K> mAlarmPriorityQueue = new AlarmPriorityQueue<>();
143     private final String mAlarmTag;
144     private final String mDumpTitle;
145     /** Whether to use an exact alarm or an inexact alarm. */
146     private final boolean mExactAlarm;
147     /** The minimum amount of time between check alarms. */
148     @GuardedBy("mLock")
149     private long mMinTimeBetweenAlarmsMs;
150     /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
151     @GuardedBy("mLock")
152     @ElapsedRealtimeLong
153     private long mTriggerTimeElapsed = NOT_SCHEDULED;
154     /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */
155     @GuardedBy("mLock")
156     @ElapsedRealtimeLong
157     private long mLastFireTimeElapsed;
158 
159     /**
160      * @param alarmTag               The tag to use when scheduling the alarm with AlarmManager.
161      * @param dumpTitle              The title to use when dumping state.
162      * @param exactAlarm             Whether or not to use an exact alarm. If false, this will use
163      *                               an inexact window alarm.
164      * @param minTimeBetweenAlarmsMs The minimum amount of time that should be between alarms. If
165      *                               one alarm will go off too soon after another, the second one
166      *                               will be delayed to meet this minimum time.
167      */
AlarmQueue(@onNull Context context, @NonNull Looper looper, @NonNull String alarmTag, @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs)168     public AlarmQueue(@NonNull Context context, @NonNull Looper looper, @NonNull String alarmTag,
169             @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs) {
170         this(context, looper, alarmTag, dumpTitle, exactAlarm, minTimeBetweenAlarmsMs,
171                 new Injector());
172     }
173 
174     @VisibleForTesting
AlarmQueue(@onNull Context context, @NonNull Looper looper, @NonNull String alarmTag, @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs, @NonNull Injector injector)175     AlarmQueue(@NonNull Context context, @NonNull Looper looper, @NonNull String alarmTag,
176             @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs,
177             @NonNull Injector injector) {
178         mContext = context;
179         mAlarmTag = alarmTag;
180         mDumpTitle = dumpTitle.trim();
181         mExactAlarm = exactAlarm;
182         mHandler = new Handler(looper);
183         mInjector = injector;
184         if (minTimeBetweenAlarmsMs < 0) {
185             throw new IllegalArgumentException("min time between alarms must be non-negative");
186         }
187         mMinTimeBetweenAlarmsMs = minTimeBetweenAlarmsMs;
188     }
189 
190     /**
191      * Add an alarm for the specified key that should go off at the provided time
192      * (in the elapsed realtime timebase). This will also remove any existing alarm for the key.
193      */
addAlarm(K key, @ElapsedRealtimeLong long alarmTimeElapsed)194     public void addAlarm(K key, @ElapsedRealtimeLong long alarmTimeElapsed) {
195         synchronized (mLock) {
196             final boolean removed = mAlarmPriorityQueue.removeKey(key);
197             mAlarmPriorityQueue.offer(new Pair<>(key, alarmTimeElapsed));
198             if (mTriggerTimeElapsed == NOT_SCHEDULED || removed
199                     || alarmTimeElapsed < mTriggerTimeElapsed) {
200                 setNextAlarmLocked();
201             }
202         }
203     }
204 
205     /**
206      * Get the current minimum time between alarms.
207      *
208      * @see #setMinTimeBetweenAlarmsMs(long)
209      */
getMinTimeBetweenAlarmsMs()210     public long getMinTimeBetweenAlarmsMs() {
211         synchronized (mLock) {
212             return mMinTimeBetweenAlarmsMs;
213         }
214     }
215 
216     /** Remove the alarm for this specific key. */
removeAlarmForKey(K key)217     public void removeAlarmForKey(K key) {
218         synchronized (mLock) {
219             if (mAlarmPriorityQueue.removeKey(key)) {
220                 setNextAlarmLocked();
221             }
222         }
223     }
224 
225     /** Remove all alarms tied to the specified user. */
removeAlarmsForUserId(@serIdInt int userId)226     public void removeAlarmsForUserId(@UserIdInt int userId) {
227         boolean removed = false;
228         synchronized (mLock) {
229             Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]);
230             for (int i = alarms.length - 1; i >= 0; --i) {
231                 final K key = (K) alarms[i].first;
232                 if (isForUser(key, userId)) {
233                     mAlarmPriorityQueue.remove(alarms[i]);
234                     removed = true;
235                 }
236             }
237             if (removed) {
238                 setNextAlarmLocked();
239             }
240         }
241     }
242 
243     /** Cancel and remove all alarms. */
removeAllAlarms()244     public void removeAllAlarms() {
245         synchronized (mLock) {
246             mAlarmPriorityQueue.clear();
247             setNextAlarmLocked(0);
248         }
249     }
250 
251     /** Remove all alarms that satisfy the predicate. */
removeAlarmsIf(@onNull Predicate<K> predicate)252     protected void removeAlarmsIf(@NonNull Predicate<K> predicate) {
253         boolean removed = false;
254         synchronized (mLock) {
255             Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]);
256             for (int i = alarms.length - 1; i >= 0; --i) {
257                 final K key = (K) alarms[i].first;
258                 if (predicate.test(key)) {
259                     mAlarmPriorityQueue.remove(alarms[i]);
260                     removed = true;
261                 }
262             }
263             if (removed) {
264                 setNextAlarmLocked();
265             }
266         }
267     }
268 
269     /**
270      * Update the minimum time that should be between alarms. This helps avoid thrashing when alarms
271      * are scheduled very closely together and may result in some batching of expired alarms.
272      */
setMinTimeBetweenAlarmsMs(long minTimeMs)273     public void setMinTimeBetweenAlarmsMs(long minTimeMs) {
274         if (minTimeMs < 0) {
275             throw new IllegalArgumentException("min time between alarms must be non-negative");
276         }
277         synchronized (mLock) {
278             mMinTimeBetweenAlarmsMs = minTimeMs;
279         }
280     }
281 
282     /** Return true if the key is for the specified user. */
isForUser(@onNull K key, int userId)283     protected abstract boolean isForUser(@NonNull K key, int userId);
284 
285     /** Handle all of the alarms that have now expired (their trigger time has passed). */
processExpiredAlarms(@onNull ArraySet<K> expired)286     protected abstract void processExpiredAlarms(@NonNull ArraySet<K> expired);
287 
288     /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */
289     @GuardedBy("mLock")
setNextAlarmLocked()290     private void setNextAlarmLocked() {
291         setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs);
292     }
293 
294     /**
295      * Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue, using
296      * {@code earliestTriggerElapsed} as a floor.
297      */
298     @GuardedBy("mLock")
setNextAlarmLocked(long earliestTriggerElapsed)299     private void setNextAlarmLocked(long earliestTriggerElapsed) {
300         if (mAlarmPriorityQueue.size() == 0) {
301             mHandler.post(() -> {
302                 // Never call out to AlarmManager with the lock held. This could sit below AM.
303                 final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
304                 if (alarmManager != null) {
305                     // This should only be null at boot time. No concerns around not
306                     // cancelling if we get null here, so no need to retry.
307                     alarmManager.cancel(this);
308                 }
309             });
310             mTriggerTimeElapsed = NOT_SCHEDULED;
311             return;
312         }
313 
314         final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
315         final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second);
316         // Only schedule the alarm if one of the following is true:
317         // 1. There isn't one currently scheduled
318         // 2. The new alarm is significantly earlier than the previous alarm. If it's
319         // earlier but not significantly so, then we essentially delay the check for some
320         // apps by up to a minute.
321         // 3. The alarm is after the current alarm.
322         final long timeShiftThresholdMs =
323                 Math.min(SIGNIFICANT_TRIGGER_TIME_CHANGE_THRESHOLD_MS, mMinTimeBetweenAlarmsMs);
324         if (mTriggerTimeElapsed == NOT_SCHEDULED
325                 || nextTriggerTimeElapsed < mTriggerTimeElapsed - timeShiftThresholdMs
326                 || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
327             if (DEBUG) {
328                 Slog.d(TAG, "Scheduling alarm at " + nextTriggerTimeElapsed
329                         + " for key " + alarm.first);
330             }
331             mTriggerTimeElapsed = nextTriggerTimeElapsed;
332             mHandler.post(mScheduleAlarmRunnable);
333         }
334     }
335 
336     @Override
onAlarm()337     public void onAlarm() {
338         final ArraySet<K> expired = new ArraySet<>();
339         synchronized (mLock) {
340             final long nowElapsed = mInjector.getElapsedRealtime();
341             mLastFireTimeElapsed = nowElapsed;
342             while (mAlarmPriorityQueue.size() > 0) {
343                 final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
344                 if (alarm.second <= nowElapsed) {
345                     expired.add(alarm.first);
346                     mAlarmPriorityQueue.remove(alarm);
347                 } else {
348                     break;
349                 }
350             }
351             setNextAlarmLocked(nowElapsed + mMinTimeBetweenAlarmsMs);
352         }
353         // Don't "call out" with the lock held to avoid potential deadlocks.
354         if (expired.size() > 0) {
355             processExpiredAlarms(expired);
356         }
357     }
358 
359     /** Dump internal state. */
dump(IndentingPrintWriter pw)360     public void dump(IndentingPrintWriter pw) {
361         synchronized (mLock) {
362             pw.print(mDumpTitle);
363             pw.println(" alarms:");
364             pw.increaseIndent();
365 
366             if (mAlarmPriorityQueue.size() == 0) {
367                 pw.println("NOT WAITING");
368             } else {
369                 Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]);
370                 for (int i = 0; i < alarms.length; ++i) {
371                     final K key = (K) alarms[i].first;
372                     pw.print(key);
373                     pw.print(": ");
374                     pw.print(alarms[i].second);
375                     pw.println();
376                 }
377             }
378 
379             pw.decreaseIndent();
380         }
381     }
382 }
383