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.dumpAlarmList; 20 21 import android.app.AlarmManager; 22 import android.util.IndentingPrintWriter; 23 import android.util.proto.ProtoOutputStream; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.util.StatLogger; 27 28 import java.text.SimpleDateFormat; 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.Comparator; 32 import java.util.function.Predicate; 33 34 /** 35 * Lazy implementation of an alarm store. 36 * This keeps the alarms in a sorted list, and only batches them at the time of delivery. 37 */ 38 public class LazyAlarmStore implements AlarmStore { 39 @VisibleForTesting 40 static final String TAG = LazyAlarmStore.class.getSimpleName(); 41 private static final long ALARM_DEADLINE_SLOP = 500; 42 43 private final ArrayList<Alarm> mAlarms = new ArrayList<>(); 44 private Runnable mOnAlarmClockRemoved; 45 46 interface Stats { 47 int GET_NEXT_DELIVERY_TIME = 0; 48 int GET_NEXT_WAKEUP_DELIVERY_TIME = 1; 49 int GET_COUNT = 2; 50 } 51 52 final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{ 53 "GET_NEXT_DELIVERY_TIME", 54 "GET_NEXT_WAKEUP_DELIVERY_TIME", 55 "GET_COUNT", 56 }); 57 58 // Decreasing time order because it is more efficient to remove from the tail of an array list. 59 private static final Comparator<Alarm> sDecreasingTimeOrder = Comparator.comparingLong( 60 Alarm::getWhenElapsed).reversed(); 61 62 @Override add(Alarm a)63 public void add(Alarm a) { 64 int index = Collections.binarySearch(mAlarms, a, sDecreasingTimeOrder); 65 if (index < 0) { 66 index = 0 - index - 1; 67 } 68 mAlarms.add(index, a); 69 } 70 71 @Override addAll(ArrayList<Alarm> alarms)72 public void addAll(ArrayList<Alarm> alarms) { 73 if (alarms == null) { 74 return; 75 } 76 mAlarms.addAll(alarms); 77 Collections.sort(mAlarms, sDecreasingTimeOrder); 78 } 79 80 @Override remove(Predicate<Alarm> whichAlarms)81 public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) { 82 final ArrayList<Alarm> removedAlarms = new ArrayList<>(); 83 for (int i = mAlarms.size() - 1; i >= 0; i--) { 84 if (whichAlarms.test(mAlarms.get(i))) { 85 final Alarm removed = mAlarms.remove(i); 86 if (removed.alarmClock != null && mOnAlarmClockRemoved != null) { 87 mOnAlarmClockRemoved.run(); 88 } 89 removedAlarms.add(removed); 90 } 91 } 92 return removedAlarms; 93 } 94 95 @Override setAlarmClockRemovalListener(Runnable listener)96 public void setAlarmClockRemovalListener(Runnable listener) { 97 mOnAlarmClockRemoved = listener; 98 } 99 100 @Override getNextWakeFromIdleAlarm()101 public Alarm getNextWakeFromIdleAlarm() { 102 for (int i = mAlarms.size() - 1; i >= 0; i--) { 103 final Alarm alarm = mAlarms.get(i); 104 if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { 105 return alarm; 106 } 107 } 108 return null; 109 } 110 111 @Override size()112 public int size() { 113 return mAlarms.size(); 114 } 115 116 @Override getNextWakeupDeliveryTime()117 public long getNextWakeupDeliveryTime() { 118 final long start = mStatLogger.getTime(); 119 long nextWakeup = 0; 120 for (int i = mAlarms.size() - 1; i >= 0; i--) { 121 final Alarm a = mAlarms.get(i); 122 if (!a.wakeup) { 123 continue; 124 } 125 if (nextWakeup == 0) { 126 nextWakeup = a.getMaxWhenElapsed(); 127 } else { 128 if (a.getWhenElapsed() > nextWakeup) { 129 break; 130 } 131 nextWakeup = Math.min(nextWakeup, a.getMaxWhenElapsed()); 132 } 133 } 134 mStatLogger.logDurationStat(Stats.GET_NEXT_WAKEUP_DELIVERY_TIME, start); 135 return nextWakeup; 136 } 137 138 @Override getNextDeliveryTime()139 public long getNextDeliveryTime() { 140 final long start = mStatLogger.getTime(); 141 final int n = mAlarms.size(); 142 if (n == 0) { 143 return 0; 144 } 145 long nextDelivery = mAlarms.get(n - 1).getMaxWhenElapsed(); 146 for (int i = n - 2; i >= 0; i--) { 147 final Alarm a = mAlarms.get(i); 148 if (a.getWhenElapsed() > nextDelivery) { 149 break; 150 } 151 nextDelivery = Math.min(nextDelivery, a.getMaxWhenElapsed()); 152 } 153 mStatLogger.logDurationStat(Stats.GET_NEXT_DELIVERY_TIME, start); 154 return nextDelivery; 155 } 156 157 @Override removePendingAlarms(long nowElapsed)158 public ArrayList<Alarm> removePendingAlarms(long nowElapsed) { 159 final ArrayList<Alarm> pending = new ArrayList<>(); 160 161 // Only send wake-up alarms if this is the absolutely latest time we can evaluate 162 // for at least one wakeup alarm. This prevents sending other non-wakeup alarms when the 163 // screen is off but the CPU is awake for some reason. 164 boolean sendWakeups = false; 165 166 // If any alarm with FLAG_STANDALONE is present, we cannot send any alarms without that flag 167 // in the present batch. 168 boolean standalonesOnly = false; 169 170 for (int i = mAlarms.size() - 1; i >= 0; i--) { 171 final Alarm alarm = mAlarms.get(i); 172 if (alarm.getWhenElapsed() > nowElapsed) { 173 break; 174 } 175 mAlarms.remove(i); 176 pending.add(alarm); 177 if (alarm.wakeup && alarm.getMaxWhenElapsed() <= nowElapsed + ALARM_DEADLINE_SLOP) { 178 // Using some slop as it is better to send the wakeup alarm now, rather than 179 // waking up again a short time later, just to send it. 180 sendWakeups = true; 181 } 182 if ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) { 183 standalonesOnly = true; 184 } 185 } 186 final ArrayList<Alarm> toSend = new ArrayList<>(); 187 for (int i = pending.size() - 1; i >= 0; i--) { 188 final Alarm pendingAlarm = pending.get(i); 189 if (!sendWakeups && pendingAlarm.wakeup) { 190 continue; 191 } 192 if (standalonesOnly && (pendingAlarm.flags & AlarmManager.FLAG_STANDALONE) == 0) { 193 continue; 194 } 195 pending.remove(i); 196 toSend.add(pendingAlarm); 197 } 198 // Perhaps some alarms could not be sent right now. Adding them back for later. 199 addAll(pending); 200 return toSend; 201 } 202 203 @Override updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator)204 public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) { 205 boolean changed = false; 206 for (final Alarm alarm : mAlarms) { 207 changed |= deliveryCalculator.updateAlarmDelivery(alarm); 208 } 209 if (changed) { 210 Collections.sort(mAlarms, sDecreasingTimeOrder); 211 } 212 return changed; 213 } 214 215 @Override asList()216 public ArrayList<Alarm> asList() { 217 final ArrayList<Alarm> copy = new ArrayList<>(mAlarms); 218 Collections.reverse(copy); 219 return copy; 220 } 221 222 @Override dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf)223 public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) { 224 ipw.println(mAlarms.size() + " pending alarms: "); 225 ipw.increaseIndent(); 226 dumpAlarmList(ipw, mAlarms, nowElapsed, sdf); 227 ipw.decreaseIndent(); 228 mStatLogger.dump(ipw); 229 } 230 231 @Override dumpProto(ProtoOutputStream pos, long nowElapsed)232 public void dumpProto(ProtoOutputStream pos, long nowElapsed) { 233 for (final Alarm a : mAlarms) { 234 a.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARMS, nowElapsed); 235 } 236 } 237 238 @Override getName()239 public String getName() { 240 return TAG; 241 } 242 243 @Override getCount(Predicate<Alarm> condition)244 public int getCount(Predicate<Alarm> condition) { 245 long start = mStatLogger.getTime(); 246 247 int count = 0; 248 for (final Alarm a : mAlarms) { 249 if (condition.test(a)) { 250 count++; 251 } 252 } 253 mStatLogger.logDurationStat(Stats.GET_COUNT, start); 254 return count; 255 } 256 } 257