blob: 0073335a1332017a0bb9e40d19c456b7efbca2a5 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.alarm;
import static com.android.server.alarm.AlarmManagerService.dumpAlarmList;
import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm;
import android.app.AlarmManager;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StatLogger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.Predicate;
/**
* Lazy implementation of an alarm store.
* This keeps the alarms in a sorted list, and only batches them at the time of delivery.
*/
public class LazyAlarmStore implements AlarmStore {
@VisibleForTesting
static final String TAG = LazyAlarmStore.class.getSimpleName();
private static final long ALARM_DEADLINE_SLOP = 500;
private final ArrayList<Alarm> mAlarms = new ArrayList<>();
private Runnable mOnAlarmClockRemoved;
interface Stats {
int GET_NEXT_DELIVERY_TIME = 0;
int GET_NEXT_WAKEUP_DELIVERY_TIME = 1;
int GET_COUNT = 2;
}
final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{
"GET_NEXT_DELIVERY_TIME",
"GET_NEXT_WAKEUP_DELIVERY_TIME",
"GET_COUNT",
});
// Decreasing time order because it is more efficient to remove from the tail of an array list.
private static final Comparator<Alarm> sDecreasingTimeOrder = Comparator.comparingLong(
Alarm::getWhenElapsed).reversed();
@Override
public void add(Alarm a) {
int index = Collections.binarySearch(mAlarms, a, sDecreasingTimeOrder);
if (index < 0) {
index = 0 - index - 1;
}
mAlarms.add(index, a);
}
@Override
public void addAll(ArrayList<Alarm> alarms) {
if (alarms == null) {
return;
}
mAlarms.addAll(alarms);
Collections.sort(mAlarms, sDecreasingTimeOrder);
}
@Override
public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) {
final ArrayList<Alarm> removedAlarms = new ArrayList<>();
for (int i = mAlarms.size() - 1; i >= 0; i--) {
if (whichAlarms.test(mAlarms.get(i))) {
final Alarm removed = mAlarms.remove(i);
if (removed.alarmClock != null && mOnAlarmClockRemoved != null) {
mOnAlarmClockRemoved.run();
}
if (isTimeTickAlarm(removed)) {
// This code path is not invoked when delivering alarms, only when removing
// alarms due to the caller cancelling it or getting uninstalled, etc.
Slog.wtf(TAG, "Removed TIME_TICK alarm");
}
removedAlarms.add(removed);
}
}
return removedAlarms;
}
@Override
public void setAlarmClockRemovalListener(Runnable listener) {
mOnAlarmClockRemoved = listener;
}
@Override
public Alarm getNextWakeFromIdleAlarm() {
for (int i = mAlarms.size() - 1; i >= 0; i--) {
final Alarm alarm = mAlarms.get(i);
if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
return alarm;
}
}
return null;
}
@Override
public int size() {
return mAlarms.size();
}
@Override
public long getNextWakeupDeliveryTime() {
final long start = mStatLogger.getTime();
long nextWakeup = 0;
for (int i = mAlarms.size() - 1; i >= 0; i--) {
final Alarm a = mAlarms.get(i);
if (!a.wakeup) {
continue;
}
if (nextWakeup == 0) {
nextWakeup = a.getMaxWhenElapsed();
} else {
if (a.getWhenElapsed() > nextWakeup) {
break;
}
nextWakeup = Math.min(nextWakeup, a.getMaxWhenElapsed());
}
}
mStatLogger.logDurationStat(Stats.GET_NEXT_WAKEUP_DELIVERY_TIME, start);
return nextWakeup;
}
@Override
public long getNextDeliveryTime() {
final long start = mStatLogger.getTime();
final int n = mAlarms.size();
if (n == 0) {
return 0;
}
long nextDelivery = mAlarms.get(n - 1).getMaxWhenElapsed();
for (int i = n - 2; i >= 0; i--) {
final Alarm a = mAlarms.get(i);
if (a.getWhenElapsed() > nextDelivery) {
break;
}
nextDelivery = Math.min(nextDelivery, a.getMaxWhenElapsed());
}
mStatLogger.logDurationStat(Stats.GET_NEXT_DELIVERY_TIME, start);
return nextDelivery;
}
@Override
public ArrayList<Alarm> removePendingAlarms(long nowElapsed) {
final ArrayList<Alarm> pending = new ArrayList<>();
// Only send wake-up alarms if this is the absolutely latest time we can evaluate
// for at least one wakeup alarm. This prevents sending other non-wakeup alarms when the
// screen is off but the CPU is awake for some reason.
boolean sendWakeups = false;
// If any alarm with FLAG_STANDALONE is present, we cannot send any alarms without that flag
// in the present batch.
boolean standalonesOnly = false;
for (int i = mAlarms.size() - 1; i >= 0; i--) {
final Alarm alarm = mAlarms.get(i);
if (alarm.getWhenElapsed() > nowElapsed) {
break;
}
mAlarms.remove(i);
pending.add(alarm);
if (alarm.wakeup && alarm.getMaxWhenElapsed() <= nowElapsed + ALARM_DEADLINE_SLOP) {
// Using some slop as it is better to send the wakeup alarm now, rather than
// waking up again a short time later, just to send it.
sendWakeups = true;
}
if ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) {
standalonesOnly = true;
}
}
final ArrayList<Alarm> toSend = new ArrayList<>();
for (int i = pending.size() - 1; i >= 0; i--) {
final Alarm pendingAlarm = pending.get(i);
if (!sendWakeups && pendingAlarm.wakeup) {
continue;
}
if (standalonesOnly && (pendingAlarm.flags & AlarmManager.FLAG_STANDALONE) == 0) {
continue;
}
pending.remove(i);
toSend.add(pendingAlarm);
}
// Perhaps some alarms could not be sent right now. Adding them back for later.
addAll(pending);
return toSend;
}
@Override
public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
boolean changed = false;
for (final Alarm alarm : mAlarms) {
changed |= deliveryCalculator.updateAlarmDelivery(alarm);
}
if (changed) {
Collections.sort(mAlarms, sDecreasingTimeOrder);
}
return changed;
}
@Override
public ArrayList<Alarm> asList() {
final ArrayList<Alarm> copy = new ArrayList<>(mAlarms);
Collections.reverse(copy);
return copy;
}
@Override
public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) {
ipw.println(mAlarms.size() + " pending alarms: ");
ipw.increaseIndent();
dumpAlarmList(ipw, mAlarms, nowElapsed, sdf);
ipw.decreaseIndent();
mStatLogger.dump(ipw);
}
@Override
public void dumpProto(ProtoOutputStream pos, long nowElapsed) {
for (final Alarm a : mAlarms) {
a.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARMS, nowElapsed);
}
}
@Override
public String getName() {
return TAG;
}
@Override
public int getCount(Predicate<Alarm> condition) {
long start = mStatLogger.getTime();
int count = 0;
for (final Alarm a : mAlarms) {
if (condition.test(a)) {
count++;
}
}
mStatLogger.logDurationStat(Stats.GET_COUNT, start);
return count;
}
}