1 /* 2 * Copyright (C) 2020 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.alarm; 18 19 import static com.android.server.alarm.AlarmManagerService.DEBUG_BATCH; 20 import static com.android.server.alarm.AlarmManagerService.clampPositive; 21 import static com.android.server.alarm.AlarmManagerService.dumpAlarmList; 22 import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm; 23 24 import android.app.AlarmManager; 25 import android.util.IndentingPrintWriter; 26 import android.util.Slog; 27 import android.util.proto.ProtoOutputStream; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.StatLogger; 31 32 import java.text.SimpleDateFormat; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.Comparator; 36 import java.util.function.Predicate; 37 38 /** 39 * Batching implementation of an Alarm Store. 40 * This keeps the alarms in batches, which are sorted on the start time of their delivery window. 41 */ 42 public class BatchingAlarmStore implements AlarmStore { 43 @VisibleForTesting 44 static final String TAG = BatchingAlarmStore.class.getSimpleName(); 45 46 private final ArrayList<Batch> mAlarmBatches = new ArrayList<>(); 47 private int mSize; 48 private Runnable mOnAlarmClockRemoved; 49 50 interface Stats { 51 int REBATCH_ALL_ALARMS = 0; 52 int GET_COUNT = 1; 53 } 54 55 final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{ 56 "REBATCH_ALL_ALARMS", 57 "GET_COUNT", 58 }); 59 60 private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart); 61 62 private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong( 63 Alarm::getWhenElapsed); 64 65 @Override add(Alarm a)66 public void add(Alarm a) { 67 insertAndBatchAlarm(a); 68 mSize++; 69 } 70 71 @Override addAll(ArrayList<Alarm> alarms)72 public void addAll(ArrayList<Alarm> alarms) { 73 if (alarms == null) { 74 return; 75 } 76 for (final Alarm a : alarms) { 77 add(a); 78 } 79 } 80 81 @Override remove(Predicate<Alarm> whichAlarms)82 public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) { 83 final ArrayList<Alarm> removed = new ArrayList<>(); 84 for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { 85 final Batch b = mAlarmBatches.get(i); 86 removed.addAll(b.remove(whichAlarms)); 87 if (b.size() == 0) { 88 mAlarmBatches.remove(i); 89 } 90 } 91 if (!removed.isEmpty()) { 92 mSize -= removed.size(); 93 // Not needed if only whole batches were removed, but keeping existing behavior. 94 rebatchAllAlarms(); 95 } 96 return removed; 97 } 98 99 @Override setAlarmClockRemovalListener(Runnable listener)100 public void setAlarmClockRemovalListener(Runnable listener) { 101 mOnAlarmClockRemoved = listener; 102 } 103 104 @Override getNextWakeFromIdleAlarm()105 public Alarm getNextWakeFromIdleAlarm() { 106 for (final Batch batch : mAlarmBatches) { 107 if ((batch.mFlags & AlarmManager.FLAG_WAKE_FROM_IDLE) == 0) { 108 continue; 109 } 110 for (int i = 0; i < batch.size(); i++) { 111 final Alarm a = batch.get(i); 112 if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { 113 return a; 114 } 115 } 116 } 117 return null; 118 } 119 rebatchAllAlarms()120 private void rebatchAllAlarms() { 121 final long start = mStatLogger.getTime(); 122 final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone(); 123 mAlarmBatches.clear(); 124 for (final Batch batch : oldBatches) { 125 for (int i = 0; i < batch.size(); i++) { 126 insertAndBatchAlarm(batch.get(i)); 127 } 128 } 129 mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start); 130 } 131 132 @Override size()133 public int size() { 134 return mSize; 135 } 136 137 @Override getNextWakeupDeliveryTime()138 public long getNextWakeupDeliveryTime() { 139 for (Batch b : mAlarmBatches) { 140 if (b.hasWakeups()) { 141 return b.mStart; 142 } 143 } 144 return 0; 145 } 146 147 @Override getNextDeliveryTime()148 public long getNextDeliveryTime() { 149 if (mAlarmBatches.size() > 0) { 150 return mAlarmBatches.get(0).mStart; 151 } 152 return 0; 153 } 154 155 @Override removePendingAlarms(long nowElapsed)156 public ArrayList<Alarm> removePendingAlarms(long nowElapsed) { 157 final ArrayList<Alarm> removedAlarms = new ArrayList<>(); 158 while (mAlarmBatches.size() > 0) { 159 final Batch batch = mAlarmBatches.get(0); 160 if (batch.mStart > nowElapsed) { 161 break; 162 } 163 mAlarmBatches.remove(0); 164 for (int i = 0; i < batch.size(); i++) { 165 removedAlarms.add(batch.get(i)); 166 } 167 } 168 mSize -= removedAlarms.size(); 169 return removedAlarms; 170 } 171 172 @Override updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator)173 public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) { 174 boolean changed = false; 175 for (final Batch b : mAlarmBatches) { 176 for (int i = 0; i < b.size(); i++) { 177 changed |= deliveryCalculator.updateAlarmDelivery(b.get(i)); 178 } 179 } 180 if (changed) { 181 rebatchAllAlarms(); 182 } 183 return changed; 184 } 185 186 @Override asList()187 public ArrayList<Alarm> asList() { 188 final ArrayList<Alarm> allAlarms = new ArrayList<>(); 189 for (final Batch batch : mAlarmBatches) { 190 for (int i = 0; i < batch.size(); i++) { 191 allAlarms.add(batch.get(i)); 192 } 193 } 194 return allAlarms; 195 } 196 197 @Override dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf)198 public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) { 199 ipw.print("Pending alarm batches: "); 200 ipw.println(mAlarmBatches.size()); 201 for (Batch b : mAlarmBatches) { 202 ipw.print(b); 203 ipw.println(':'); 204 ipw.increaseIndent(); 205 dumpAlarmList(ipw, b.mAlarms, nowElapsed, sdf); 206 ipw.decreaseIndent(); 207 } 208 mStatLogger.dump(ipw); 209 } 210 211 @Override dumpProto(ProtoOutputStream pos, long nowElapsed)212 public void dumpProto(ProtoOutputStream pos, long nowElapsed) { 213 for (Batch b : mAlarmBatches) { 214 b.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARM_BATCHES, nowElapsed); 215 } 216 } 217 218 @Override getName()219 public String getName() { 220 return TAG; 221 } 222 223 @Override getCount(Predicate<Alarm> condition)224 public int getCount(Predicate<Alarm> condition) { 225 long start = mStatLogger.getTime(); 226 227 int count = 0; 228 for (Batch b : mAlarmBatches) { 229 for (int i = 0; i < b.size(); i++) { 230 if (condition.test(b.get(i))) { 231 count++; 232 } 233 } 234 } 235 mStatLogger.logDurationStat(Stats.GET_COUNT, start); 236 return count; 237 } 238 insertAndBatchAlarm(Alarm alarm)239 private void insertAndBatchAlarm(Alarm alarm) { 240 final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1 241 : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed()); 242 243 if (whichBatch < 0) { 244 addBatch(mAlarmBatches, new Batch(alarm)); 245 } else { 246 final Batch batch = mAlarmBatches.get(whichBatch); 247 if (batch.add(alarm)) { 248 // The start time of this batch advanced, so batch ordering may 249 // have just been broken. Move it to where it now belongs. 250 mAlarmBatches.remove(whichBatch); 251 addBatch(mAlarmBatches, batch); 252 } 253 } 254 } 255 addBatch(ArrayList<Batch> list, Batch newBatch)256 static void addBatch(ArrayList<Batch> list, Batch newBatch) { 257 int index = Collections.binarySearch(list, newBatch, sBatchOrder); 258 if (index < 0) { 259 index = 0 - index - 1; 260 } 261 list.add(index, newBatch); 262 } 263 264 // Return the index of the matching batch, or -1 if none found. attemptCoalesce(long whenElapsed, long maxWhen)265 private int attemptCoalesce(long whenElapsed, long maxWhen) { 266 final int n = mAlarmBatches.size(); 267 for (int i = 0; i < n; i++) { 268 Batch b = mAlarmBatches.get(i); 269 if ((b.mFlags & AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) { 270 return i; 271 } 272 } 273 return -1; 274 } 275 276 final class Batch { 277 long mStart; // These endpoints are always in ELAPSED 278 long mEnd; 279 int mFlags; // Flags for alarms, such as FLAG_STANDALONE. 280 281 final ArrayList<Alarm> mAlarms = new ArrayList<>(); 282 Batch(Alarm seed)283 Batch(Alarm seed) { 284 mStart = seed.getWhenElapsed(); 285 mEnd = clampPositive(seed.getMaxWhenElapsed()); 286 mFlags = seed.flags; 287 mAlarms.add(seed); 288 } 289 size()290 int size() { 291 return mAlarms.size(); 292 } 293 get(int index)294 Alarm get(int index) { 295 return mAlarms.get(index); 296 } 297 canHold(long whenElapsed, long maxWhen)298 boolean canHold(long whenElapsed, long maxWhen) { 299 return (mEnd >= whenElapsed) && (mStart <= maxWhen); 300 } 301 add(Alarm alarm)302 boolean add(Alarm alarm) { 303 boolean newStart = false; 304 // narrows the batch if necessary; presumes that canHold(alarm) is true 305 int index = Collections.binarySearch(mAlarms, alarm, sIncreasingTimeOrder); 306 if (index < 0) { 307 index = 0 - index - 1; 308 } 309 mAlarms.add(index, alarm); 310 if (DEBUG_BATCH) { 311 Slog.v(TAG, "Adding " + alarm + " to " + this); 312 } 313 if (alarm.getWhenElapsed() > mStart) { 314 mStart = alarm.getWhenElapsed(); 315 newStart = true; 316 } 317 if (alarm.getMaxWhenElapsed() < mEnd) { 318 mEnd = alarm.getMaxWhenElapsed(); 319 } 320 mFlags |= alarm.flags; 321 322 if (DEBUG_BATCH) { 323 Slog.v(TAG, " => now " + this); 324 } 325 return newStart; 326 } 327 remove(Predicate<Alarm> predicate)328 ArrayList<Alarm> remove(Predicate<Alarm> predicate) { 329 final ArrayList<Alarm> removed = new ArrayList<>(); 330 long newStart = 0; // recalculate endpoints as we go 331 long newEnd = Long.MAX_VALUE; 332 int newFlags = 0; 333 for (int i = 0; i < mAlarms.size(); ) { 334 Alarm alarm = mAlarms.get(i); 335 if (predicate.test(alarm)) { 336 removed.add(mAlarms.remove(i)); 337 if (alarm.alarmClock != null && mOnAlarmClockRemoved != null) { 338 mOnAlarmClockRemoved.run(); 339 } 340 if (isTimeTickAlarm(alarm)) { 341 // This code path is not invoked when delivering alarms, only when removing 342 // alarms due to the caller cancelling it or getting uninstalled, etc. 343 Slog.wtf(TAG, "Removed TIME_TICK alarm"); 344 } 345 } else { 346 if (alarm.getWhenElapsed() > newStart) { 347 newStart = alarm.getWhenElapsed(); 348 } 349 if (alarm.getMaxWhenElapsed() < newEnd) { 350 newEnd = alarm.getMaxWhenElapsed(); 351 } 352 newFlags |= alarm.flags; 353 i++; 354 } 355 } 356 if (!removed.isEmpty()) { 357 // commit the new batch bounds 358 mStart = newStart; 359 mEnd = newEnd; 360 mFlags = newFlags; 361 } 362 return removed; 363 } 364 hasWakeups()365 boolean hasWakeups() { 366 final int n = mAlarms.size(); 367 for (int i = 0; i < n; i++) { 368 Alarm a = mAlarms.get(i); 369 if (a.wakeup) { 370 return true; 371 } 372 } 373 return false; 374 } 375 376 @Override toString()377 public String toString() { 378 StringBuilder b = new StringBuilder(40); 379 b.append("Batch{"); 380 b.append(Integer.toHexString(this.hashCode())); 381 b.append(" num="); 382 b.append(size()); 383 b.append(" start="); 384 b.append(mStart); 385 b.append(" end="); 386 b.append(mEnd); 387 if (mFlags != 0) { 388 b.append(" flgs=0x"); 389 b.append(Integer.toHexString(mFlags)); 390 } 391 b.append('}'); 392 return b.toString(); 393 } 394 dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed)395 public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { 396 final long token = proto.start(fieldId); 397 398 proto.write(BatchProto.START_REALTIME, mStart); 399 proto.write(BatchProto.END_REALTIME, mEnd); 400 proto.write(BatchProto.FLAGS, mFlags); 401 for (Alarm a : mAlarms) { 402 a.dumpDebug(proto, BatchProto.ALARMS, nowElapsed); 403 } 404 405 proto.end(token); 406 } 407 } 408 } 409