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 /** 58 * Internal priority queue for each key's alarm, ordered by the time the alarm should go off. 59 * The pair is the key and its associated alarm time (in the elapsed realtime timebase). 60 */ 61 private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> { 62 private static final Comparator<Pair<?, Long>> sTimeComparator = 63 (o1, o2) -> Long.compare(o1.second, o2.second); 64 AlarmPriorityQueue()65 AlarmPriorityQueue() { 66 super(1, sTimeComparator); 67 } 68 69 /** 70 * Remove any instances of the key from the queue. 71 * 72 * @return true if an instance was removed, false otherwise. 73 */ removeKey(@onNull Q key)74 public boolean removeKey(@NonNull Q key) { 75 boolean removed = false; 76 Pair[] alarms = toArray(new Pair[size()]); 77 for (int i = alarms.length - 1; i >= 0; --i) { 78 if (key.equals(alarms[i].first)) { 79 remove(alarms[i]); 80 removed = true; 81 } 82 } 83 return removed; 84 } 85 } 86 87 @VisibleForTesting 88 static class Injector { getElapsedRealtime()89 long getElapsedRealtime() { 90 return SystemClock.elapsedRealtime(); 91 } 92 } 93 94 /** Runnable used to schedule an alarm with AlarmManager. NEVER run this with the lock held. */ 95 private final Runnable mScheduleAlarmRunnable = new Runnable() { 96 @Override 97 public void run() { 98 mHandler.removeCallbacks(this); 99 100 final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); 101 if (alarmManager == null) { 102 // The system isn't fully booted. Clients of this class may not have 103 // direct access to (be notified when) the system is ready, so retry 104 // setting the alarm after some delay. Leave enough time so that we don't cause 105 // any unneeded startup delay. 106 mHandler.postDelayed(this, 30_000); 107 return; 108 } 109 final long nextTriggerTimeElapsed; 110 final long minTimeBetweenAlarmsMs; 111 synchronized (mLock) { 112 if (mTriggerTimeElapsed == NOT_SCHEDULED) { 113 return; 114 } 115 nextTriggerTimeElapsed = mTriggerTimeElapsed; 116 minTimeBetweenAlarmsMs = mMinTimeBetweenAlarmsMs; 117 } 118 // Never call out to AlarmManager with the lock held. This could sit below AM. 119 if (mExactAlarm) { 120 alarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 121 nextTriggerTimeElapsed, mAlarmTag, AlarmQueue.this, mHandler); 122 } else { 123 alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME, 124 nextTriggerTimeElapsed, minTimeBetweenAlarmsMs / 2, 125 mAlarmTag, AlarmQueue.this, mHandler); 126 } 127 } 128 }; 129 130 private final Object mLock = new Object(); 131 132 private final Context mContext; 133 private final Handler mHandler; 134 private final Injector mInjector; 135 136 @GuardedBy("mLock") 137 private final AlarmPriorityQueue<K> mAlarmPriorityQueue = new AlarmPriorityQueue<>(); 138 private final String mAlarmTag; 139 private final String mDumpTitle; 140 /** Whether to use an exact alarm or an inexact alarm. */ 141 private final boolean mExactAlarm; 142 /** The minimum amount of time between check alarms. */ 143 @GuardedBy("mLock") 144 private long mMinTimeBetweenAlarmsMs; 145 /** The next time the alarm is set to go off, in the elapsed realtime timebase. */ 146 @GuardedBy("mLock") 147 @ElapsedRealtimeLong 148 private long mTriggerTimeElapsed = NOT_SCHEDULED; 149 150 /** 151 * @param alarmTag The tag to use when scheduling the alarm with AlarmManager. 152 * @param dumpTitle The title to use when dumping state. 153 * @param exactAlarm Whether or not to use an exact alarm. If false, this will use 154 * an inexact window alarm. 155 * @param minTimeBetweenAlarmsMs The minimum amount of time that should be between alarms. If 156 * one alarm will go off too soon after another, the second one 157 * will be delayed to meet this minimum time. 158 */ AlarmQueue(@onNull Context context, @NonNull Looper looper, @NonNull String alarmTag, @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs)159 public AlarmQueue(@NonNull Context context, @NonNull Looper looper, @NonNull String alarmTag, 160 @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs) { 161 this(context, looper, alarmTag, dumpTitle, exactAlarm, minTimeBetweenAlarmsMs, 162 new Injector()); 163 } 164 165 @VisibleForTesting AlarmQueue(@onNull Context context, @NonNull Looper looper, @NonNull String alarmTag, @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs, @NonNull Injector injector)166 AlarmQueue(@NonNull Context context, @NonNull Looper looper, @NonNull String alarmTag, 167 @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs, 168 @NonNull Injector injector) { 169 mContext = context; 170 mAlarmTag = alarmTag; 171 mDumpTitle = dumpTitle.trim(); 172 mExactAlarm = exactAlarm; 173 mHandler = new Handler(looper); 174 mInjector = injector; 175 if (minTimeBetweenAlarmsMs < 0) { 176 throw new IllegalArgumentException("min time between alarms must be non-negative"); 177 } 178 mMinTimeBetweenAlarmsMs = minTimeBetweenAlarmsMs; 179 } 180 181 /** 182 * Add an alarm for the specified key that should go off at the provided time 183 * (in the elapsed realtime timebase). This will also remove any existing alarm for the key. 184 */ addAlarm(K key, @ElapsedRealtimeLong long alarmTimeElapsed)185 public void addAlarm(K key, @ElapsedRealtimeLong long alarmTimeElapsed) { 186 synchronized (mLock) { 187 final boolean removed = mAlarmPriorityQueue.removeKey(key); 188 mAlarmPriorityQueue.offer(new Pair<>(key, alarmTimeElapsed)); 189 if (mTriggerTimeElapsed == NOT_SCHEDULED || removed 190 || alarmTimeElapsed < mTriggerTimeElapsed) { 191 setNextAlarmLocked(); 192 } 193 } 194 } 195 196 /** 197 * Get the current minimum time between alarms. 198 * 199 * @see #setMinTimeBetweenAlarmsMs(long) 200 */ getMinTimeBetweenAlarmsMs()201 public long getMinTimeBetweenAlarmsMs() { 202 synchronized (mLock) { 203 return mMinTimeBetweenAlarmsMs; 204 } 205 } 206 207 /** Remove the alarm for this specific key. */ removeAlarmForKey(K key)208 public void removeAlarmForKey(K key) { 209 synchronized (mLock) { 210 if (mAlarmPriorityQueue.removeKey(key)) { 211 setNextAlarmLocked(); 212 } 213 } 214 } 215 216 /** Remove all alarms tied to the specified user. */ removeAlarmsForUserId(@serIdInt int userId)217 public void removeAlarmsForUserId(@UserIdInt int userId) { 218 boolean removed = false; 219 synchronized (mLock) { 220 Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]); 221 for (int i = alarms.length - 1; i >= 0; --i) { 222 final K key = (K) alarms[i].first; 223 if (isForUser(key, userId)) { 224 mAlarmPriorityQueue.remove(alarms[i]); 225 removed = true; 226 } 227 } 228 if (removed) { 229 setNextAlarmLocked(); 230 } 231 } 232 } 233 234 /** Cancel and remove all alarms. */ removeAllAlarms()235 public void removeAllAlarms() { 236 synchronized (mLock) { 237 mAlarmPriorityQueue.clear(); 238 setNextAlarmLocked(0); 239 } 240 } 241 242 /** Remove all alarms that satisfy the predicate. */ removeAlarmsIf(@onNull Predicate<K> predicate)243 protected void removeAlarmsIf(@NonNull Predicate<K> predicate) { 244 boolean removed = false; 245 synchronized (mLock) { 246 Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]); 247 for (int i = alarms.length - 1; i >= 0; --i) { 248 final K key = (K) alarms[i].first; 249 if (predicate.test(key)) { 250 mAlarmPriorityQueue.remove(alarms[i]); 251 removed = true; 252 } 253 } 254 if (removed) { 255 setNextAlarmLocked(); 256 } 257 } 258 } 259 260 /** 261 * Update the minimum time that should be between alarms. This helps avoid thrashing when alarms 262 * are scheduled very closely together and may result in some batching of expired alarms. 263 */ setMinTimeBetweenAlarmsMs(long minTimeMs)264 public void setMinTimeBetweenAlarmsMs(long minTimeMs) { 265 if (minTimeMs < 0) { 266 throw new IllegalArgumentException("min time between alarms must be non-negative"); 267 } 268 synchronized (mLock) { 269 mMinTimeBetweenAlarmsMs = minTimeMs; 270 } 271 } 272 273 /** Return true if the key is for the specified user. */ isForUser(@onNull K key, int userId)274 protected abstract boolean isForUser(@NonNull K key, int userId); 275 276 /** Handle all of the alarms that have now expired (their trigger time has passed). */ processExpiredAlarms(@onNull ArraySet<K> expired)277 protected abstract void processExpiredAlarms(@NonNull ArraySet<K> expired); 278 279 /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */ 280 @GuardedBy("mLock") setNextAlarmLocked()281 private void setNextAlarmLocked() { 282 setNextAlarmLocked(mInjector.getElapsedRealtime()); 283 } 284 285 /** 286 * Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue, using 287 * {@code earliestTriggerElapsed} as a floor. 288 */ 289 @GuardedBy("mLock") setNextAlarmLocked(long earliestTriggerElapsed)290 private void setNextAlarmLocked(long earliestTriggerElapsed) { 291 if (mAlarmPriorityQueue.size() == 0) { 292 mHandler.post(() -> { 293 // Never call out to AlarmManager with the lock held. This could sit below AM. 294 final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); 295 if (alarmManager != null) { 296 // This should only be null at boot time. No concerns around not 297 // cancelling if we get null here, so no need to retry. 298 alarmManager.cancel(this); 299 } 300 }); 301 mTriggerTimeElapsed = NOT_SCHEDULED; 302 return; 303 } 304 305 final Pair<K, Long> alarm = mAlarmPriorityQueue.peek(); 306 final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second); 307 // Only schedule the alarm if one of the following is true: 308 // 1. There isn't one currently scheduled 309 // 2. The new alarm is significantly earlier than the previous alarm. If it's 310 // earlier but not significantly so, then we essentially delay the check for some 311 // apps by up to a minute. 312 // 3. The alarm is after the current alarm. 313 if (mTriggerTimeElapsed == NOT_SCHEDULED 314 || nextTriggerTimeElapsed < mTriggerTimeElapsed - MINUTE_IN_MILLIS 315 || mTriggerTimeElapsed < nextTriggerTimeElapsed) { 316 if (DEBUG) { 317 Slog.d(TAG, "Scheduling alarm at " + nextTriggerTimeElapsed 318 + " for key " + alarm.first); 319 } 320 mTriggerTimeElapsed = nextTriggerTimeElapsed; 321 mHandler.post(mScheduleAlarmRunnable); 322 } 323 } 324 325 @Override onAlarm()326 public void onAlarm() { 327 final ArraySet<K> expired = new ArraySet<>(); 328 synchronized (mLock) { 329 final long nowElapsed = mInjector.getElapsedRealtime(); 330 while (mAlarmPriorityQueue.size() > 0) { 331 final Pair<K, Long> alarm = mAlarmPriorityQueue.peek(); 332 if (alarm.second <= nowElapsed) { 333 expired.add(alarm.first); 334 mAlarmPriorityQueue.remove(alarm); 335 } else { 336 break; 337 } 338 } 339 setNextAlarmLocked(nowElapsed + mMinTimeBetweenAlarmsMs); 340 } 341 // Don't "call out" with the lock held to avoid potential deadlocks. 342 if (expired.size() > 0) { 343 processExpiredAlarms(expired); 344 } 345 } 346 347 /** Dump internal state. */ dump(IndentingPrintWriter pw)348 public void dump(IndentingPrintWriter pw) { 349 synchronized (mLock) { 350 pw.print(mDumpTitle); 351 pw.println(" alarms:"); 352 pw.increaseIndent(); 353 354 if (mAlarmPriorityQueue.size() == 0) { 355 pw.println("NOT WAITING"); 356 } else { 357 Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]); 358 for (int i = 0; i < alarms.length; ++i) { 359 final K key = (K) alarms[i].first; 360 pw.print(key); 361 pw.print(": "); 362 pw.print(alarms[i].second); 363 pw.println(); 364 } 365 } 366 367 pw.decreaseIndent(); 368 } 369 } 370 } 371