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