• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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