Merge "Introducing a lazy alarm store"
This commit is contained in:
committed by
Android (Google) Code Review
commit
70b439562c
@@ -268,6 +268,7 @@ public class AlarmManagerService extends SystemService {
|
||||
*/
|
||||
Bundle mIdleOptions;
|
||||
|
||||
// TODO(b/172085676): Move inside alarm store.
|
||||
private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
|
||||
new SparseArray<>();
|
||||
private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray =
|
||||
@@ -276,6 +277,9 @@ public class AlarmManagerService extends SystemService {
|
||||
new SparseBooleanArray();
|
||||
private boolean mNextAlarmClockMayChange;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true;
|
||||
|
||||
// May only use on mHandler's thread, locking not required.
|
||||
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
|
||||
new SparseArray<>();
|
||||
@@ -410,6 +414,9 @@ public class AlarmManagerService extends SystemService {
|
||||
private static final String KEY_APP_STANDBY_RESTRICTED_WINDOW =
|
||||
"app_standby_restricted_window";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String KEY_LAZY_BATCHING = "lazy_batching";
|
||||
|
||||
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
|
||||
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
|
||||
private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
|
||||
@@ -432,6 +439,8 @@ public class AlarmManagerService extends SystemService {
|
||||
private static final int DEFAULT_APP_STANDBY_RESTRICTED_QUOTA = 1;
|
||||
private static final long DEFAULT_APP_STANDBY_RESTRICTED_WINDOW = MILLIS_IN_DAY;
|
||||
|
||||
private static final boolean DEFAULT_LAZY_BATCHING = false;
|
||||
|
||||
// Minimum futurity of a new alarm
|
||||
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
|
||||
|
||||
@@ -460,6 +469,8 @@ public class AlarmManagerService extends SystemService {
|
||||
public int APP_STANDBY_RESTRICTED_QUOTA = DEFAULT_APP_STANDBY_RESTRICTED_QUOTA;
|
||||
public long APP_STANDBY_RESTRICTED_WINDOW = DEFAULT_APP_STANDBY_RESTRICTED_WINDOW;
|
||||
|
||||
public boolean LAZY_BATCHING = DEFAULT_LAZY_BATCHING;
|
||||
|
||||
private long mLastAllowWhileIdleWhitelistDuration = -1;
|
||||
|
||||
Constants() {
|
||||
@@ -538,6 +549,14 @@ public class AlarmManagerService extends SystemService {
|
||||
case KEY_APP_STANDBY_RESTRICTED_WINDOW:
|
||||
updateStandbyWindowsLocked();
|
||||
break;
|
||||
case KEY_LAZY_BATCHING:
|
||||
final boolean oldLazyBatching = LAZY_BATCHING;
|
||||
LAZY_BATCHING = properties.getBoolean(
|
||||
KEY_LAZY_BATCHING, DEFAULT_LAZY_BATCHING);
|
||||
if (oldLazyBatching != LAZY_BATCHING) {
|
||||
migrateAlarmsToNewStoreLocked();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
|
||||
// The quotas need to be updated in order, so we can't just rely
|
||||
@@ -551,6 +570,15 @@ public class AlarmManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateAlarmsToNewStoreLocked() {
|
||||
final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore()
|
||||
: new BatchingAlarmStore();
|
||||
final ArrayList<Alarm> allAlarms = mAlarmStore.remove((unused) -> true);
|
||||
newStore.addAll(allAlarms);
|
||||
mAlarmStore = newStore;
|
||||
mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
|
||||
}
|
||||
|
||||
private void updateStandbyQuotasLocked() {
|
||||
// The bucket quotas need to be read as an atomic unit but the properties passed to
|
||||
// onPropertiesChanged may only have one key populated at a time.
|
||||
@@ -659,6 +687,9 @@ public class AlarmManagerService extends SystemService {
|
||||
TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw);
|
||||
pw.println();
|
||||
|
||||
pw.print(KEY_LAZY_BATCHING, LAZY_BATCHING);
|
||||
pw.println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
@@ -770,7 +801,7 @@ public class AlarmManagerService extends SystemService {
|
||||
// minimum recurrence period or alarm futurity for us to be able to fuzz it
|
||||
static final long MIN_FUZZABLE_INTERVAL = 10000;
|
||||
@GuardedBy("mLock")
|
||||
final AlarmStore mAlarmStore;
|
||||
AlarmStore mAlarmStore;
|
||||
|
||||
// set to non-null if in idle mode; while in this mode, any alarms we don't want
|
||||
// to run during this time are rescehduled to go off after this alarm.
|
||||
@@ -781,7 +812,6 @@ public class AlarmManagerService extends SystemService {
|
||||
AlarmManagerService(Context context, Injector injector) {
|
||||
super(context);
|
||||
mInjector = injector;
|
||||
mAlarmStore = new BatchingAlarmStore(() -> mNextAlarmClockMayChange = true);
|
||||
}
|
||||
|
||||
public AlarmManagerService(Context context) {
|
||||
@@ -1219,6 +1249,11 @@ public class AlarmManagerService extends SystemService {
|
||||
synchronized (mLock) {
|
||||
mHandler = new AlarmHandler();
|
||||
mConstants = new Constants();
|
||||
|
||||
mAlarmStore = mConstants.LAZY_BATCHING ? new LazyAlarmStore()
|
||||
: new BatchingAlarmStore();
|
||||
mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
|
||||
|
||||
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
|
||||
|
||||
mNextWakeup = mNextNonWakeup = 0;
|
||||
@@ -3055,12 +3090,13 @@ public class AlarmManagerService extends SystemService {
|
||||
|
||||
static final void dumpAlarmList(IndentingPrintWriter ipw, ArrayList<Alarm> list,
|
||||
long nowELAPSED, SimpleDateFormat sdf) {
|
||||
for (int i = list.size() - 1; i >= 0; i--) {
|
||||
final int n = list.size();
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
final Alarm a = list.get(i);
|
||||
final String label = Alarm.typeToString(a.type);
|
||||
ipw.print(label);
|
||||
ipw.print(" #");
|
||||
ipw.print(i);
|
||||
ipw.print(n - i);
|
||||
ipw.print(": ");
|
||||
ipw.println(a);
|
||||
ipw.increaseIndent();
|
||||
|
||||
@@ -39,6 +39,13 @@ public interface AlarmStore {
|
||||
*/
|
||||
void add(Alarm a);
|
||||
|
||||
/**
|
||||
* Adds all the given alarms to this store.
|
||||
*
|
||||
* @param alarms The alarms to add.
|
||||
*/
|
||||
void addAll(ArrayList<Alarm> alarms);
|
||||
|
||||
/**
|
||||
* Removes alarms that pass the given predicate.
|
||||
*
|
||||
@@ -47,12 +54,18 @@ public interface AlarmStore {
|
||||
*/
|
||||
ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms);
|
||||
|
||||
/**
|
||||
* Set a listener to be invoked whenever an alarm clock is removed by a call to
|
||||
* {@link #remove(Predicate) remove} from this store.
|
||||
*/
|
||||
void setAlarmClockRemovalListener(Runnable listener);
|
||||
|
||||
/**
|
||||
* Gets the earliest alarm with the flag {@link android.app.AlarmManager#FLAG_WAKE_FROM_IDLE}
|
||||
* based on {@link Alarm#getWhenElapsed()}.
|
||||
*
|
||||
* @return An alarm object matching the description above or {@code null} if no such alarm was
|
||||
* found.
|
||||
* found.
|
||||
*/
|
||||
Alarm getNextWakeFromIdleAlarm();
|
||||
|
||||
|
||||
@@ -41,45 +41,22 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public class BatchingAlarmStore implements AlarmStore {
|
||||
|
||||
private ArrayList<Batch> mAlarmBatches = new ArrayList<>();
|
||||
private final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
|
||||
private int mSize;
|
||||
private AlarmClockRemovalListener mAlarmClockRemovalListener;
|
||||
private Runnable mOnAlarmClockRemoved;
|
||||
|
||||
interface Stats {
|
||||
int REBATCH_ALL_ALARMS = 0;
|
||||
}
|
||||
|
||||
final StatLogger mStatLogger = new StatLogger("Alarm store stats", new String[]{
|
||||
final StatLogger mStatLogger = new StatLogger("BatchingAlarmStore stats", new String[]{
|
||||
"REBATCH_ALL_ALARMS",
|
||||
});
|
||||
|
||||
private static final Comparator<Batch> sBatchOrder = (b1, b2) -> {
|
||||
long when1 = b1.mStart;
|
||||
long when2 = b2.mStart;
|
||||
if (when1 > when2) {
|
||||
return 1;
|
||||
}
|
||||
if (when1 < when2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart);
|
||||
|
||||
private static final Comparator<Alarm> sIncreasingTimeOrder = (a1, a2) -> {
|
||||
long when1 = a1.getWhenElapsed();
|
||||
long when2 = a2.getWhenElapsed();
|
||||
if (when1 > when2) {
|
||||
return 1;
|
||||
}
|
||||
if (when1 < when2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
BatchingAlarmStore(AlarmClockRemovalListener listener) {
|
||||
mAlarmClockRemovalListener = listener;
|
||||
}
|
||||
private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong(
|
||||
Alarm::getWhenElapsed);
|
||||
|
||||
@Override
|
||||
public void add(Alarm a) {
|
||||
@@ -87,6 +64,16 @@ public class BatchingAlarmStore implements AlarmStore {
|
||||
mSize++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(ArrayList<Alarm> alarms) {
|
||||
if (alarms == null) {
|
||||
return;
|
||||
}
|
||||
for (final Alarm a : alarms) {
|
||||
add(a);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) {
|
||||
final ArrayList<Alarm> removed = new ArrayList<>();
|
||||
@@ -105,6 +92,11 @@ public class BatchingAlarmStore implements AlarmStore {
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlarmClockRemovalListener(Runnable listener) {
|
||||
mOnAlarmClockRemoved = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alarm getNextWakeFromIdleAlarm() {
|
||||
for (final Batch batch : mAlarmBatches) {
|
||||
@@ -317,8 +309,8 @@ public class BatchingAlarmStore implements AlarmStore {
|
||||
Alarm alarm = mAlarms.get(i);
|
||||
if (predicate.test(alarm)) {
|
||||
removed.add(mAlarms.remove(i));
|
||||
if (alarm.alarmClock != null && mAlarmClockRemovalListener != null) {
|
||||
mAlarmClockRemovalListener.onRemoved();
|
||||
if (alarm.alarmClock != null && mOnAlarmClockRemoved != null) {
|
||||
mOnAlarmClockRemoved.run();
|
||||
}
|
||||
if (isTimeTickAlarm(alarm)) {
|
||||
// This code path is not invoked when delivering alarms, only when removing
|
||||
@@ -388,9 +380,4 @@ public class BatchingAlarmStore implements AlarmStore {
|
||||
proto.end(token);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface AlarmClockRemovalListener {
|
||||
void onRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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.TAG;
|
||||
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.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 {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
final StatLogger mStatLogger = new StatLogger("LazyAlarmStore stats", new String[]{
|
||||
"GET_NEXT_DELIVERY_TIME",
|
||||
"GET_NEXT_WAKEUP_DELIVERY_TIME",
|
||||
});
|
||||
|
||||
// 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(alarms, 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<>();
|
||||
final ArrayList<Alarm> standAlones = new ArrayList<>();
|
||||
|
||||
for (int i = mAlarms.size() - 1; i >= 0; i--) {
|
||||
final Alarm alarm = mAlarms.get(i);
|
||||
if (alarm.getWhenElapsed() > nowElapsed) {
|
||||
break;
|
||||
}
|
||||
pending.add(alarm);
|
||||
if ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) {
|
||||
standAlones.add(alarm);
|
||||
}
|
||||
}
|
||||
if (!standAlones.isEmpty()) {
|
||||
// If there are deliverable standalone alarms, others must not go out yet.
|
||||
mAlarms.removeAll(standAlones);
|
||||
return standAlones;
|
||||
}
|
||||
mAlarms.removeAll(pending);
|
||||
return pending;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,8 @@ message AlarmManagerServiceDumpProto {
|
||||
|
||||
repeated IdleDispatchEntryProto allow_while_idle_dispatches = 40;
|
||||
repeated WakeupEventProto recent_wakeup_history = 41;
|
||||
|
||||
repeated AlarmProto pending_alarms = 42;
|
||||
}
|
||||
|
||||
// This is a soft wrapper for alarm clock information. It is not representative
|
||||
|
||||
@@ -47,6 +47,7 @@ import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_F
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
|
||||
@@ -62,6 +63,7 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -365,7 +367,7 @@ public class AlarmManagerServiceTest {
|
||||
}
|
||||
|
||||
private void setIdleUntilAlarm(int type, long triggerTime, PendingIntent pi) {
|
||||
setTestAlarm(type, triggerTime, pi, 0, FLAG_IDLE_UNTIL, TEST_CALLING_UID);
|
||||
setTestAlarm(type, triggerTime, pi, 0, FLAG_IDLE_UNTIL | FLAG_STANDALONE, TEST_CALLING_UID);
|
||||
}
|
||||
|
||||
private void setWakeFromIdle(int type, long triggerTime, PendingIntent pi) {
|
||||
@@ -410,6 +412,12 @@ public class AlarmManagerServiceTest {
|
||||
mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
|
||||
}
|
||||
|
||||
private void setDeviceConfigBoolean(String key, boolean val) {
|
||||
mDeviceConfigKeys.add(key);
|
||||
doReturn(val).when(mDeviceConfigProperties).getBoolean(eq(key), anyBoolean());
|
||||
mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowers quotas to make testing feasible. Careful while calling as this will replace any
|
||||
* existing settings for the calling test.
|
||||
@@ -1382,6 +1390,35 @@ public class AlarmManagerServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alarmStoreMigration() {
|
||||
setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);
|
||||
final int numAlarms = 10;
|
||||
final PendingIntent[] pis = new PendingIntent[numAlarms];
|
||||
for (int i = 0; i < numAlarms; i++) {
|
||||
pis[i] = getNewMockPendingIntent();
|
||||
setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, pis[i]);
|
||||
}
|
||||
|
||||
final ArrayList<Alarm> alarmsBefore = mService.mAlarmStore.asList();
|
||||
assertEquals(numAlarms, alarmsBefore.size());
|
||||
for (int i = 0; i < numAlarms; i++) {
|
||||
final PendingIntent pi = pis[i];
|
||||
assertTrue(i + "th PendingIntent missing: ",
|
||||
alarmsBefore.removeIf(a -> a.matches(pi, null)));
|
||||
}
|
||||
|
||||
setDeviceConfigBoolean(KEY_LAZY_BATCHING, true);
|
||||
|
||||
final ArrayList<Alarm> alarmsAfter = mService.mAlarmStore.asList();
|
||||
assertEquals(numAlarms, alarmsAfter.size());
|
||||
for (int i = 0; i < numAlarms; i++) {
|
||||
final PendingIntent pi = pis[i];
|
||||
assertTrue(i + "th PendingIntent missing: ",
|
||||
alarmsAfter.removeIf(a -> a.matches(pi, null)));
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (mMockingSession != null) {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.server.alarm;
|
||||
|
||||
import static android.app.AlarmManager.ELAPSED_REALTIME;
|
||||
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
|
||||
|
||||
import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
|
||||
import static com.android.server.alarm.Constants.TEST_CALLING_UID;
|
||||
|
||||
@@ -23,35 +26,50 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@RunWith(Parameterized.class)
|
||||
public class AlarmStoreTest {
|
||||
private AlarmStore mAlarmStore;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mAlarmStore = new BatchingAlarmStore(null);
|
||||
@Parameter
|
||||
public AlarmStore mAlarmStore;
|
||||
|
||||
@Parameters
|
||||
public static Object[] stores() {
|
||||
return new AlarmStore[]{
|
||||
new LazyAlarmStore(),
|
||||
new BatchingAlarmStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private static Alarm createAlarm(long whenElapsed, long windowLength) {
|
||||
return createAlarm(AlarmManager.ELAPSED_REALTIME, whenElapsed, windowLength, 0);
|
||||
return createAlarm(ELAPSED_REALTIME, whenElapsed, windowLength, 0);
|
||||
}
|
||||
|
||||
private static Alarm createWakeupAlarm(long whenElapsed, long windowLength, int flags) {
|
||||
return createAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength, flags);
|
||||
return createAlarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength, flags);
|
||||
}
|
||||
|
||||
private static Alarm createAlarmClock(long whenElapsed) {
|
||||
final AlarmManager.AlarmClockInfo info = new AlarmManager.AlarmClockInfo(whenElapsed,
|
||||
mock(PendingIntent.class));
|
||||
return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0,
|
||||
mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID,
|
||||
TEST_CALLING_PACKAGE);
|
||||
}
|
||||
|
||||
private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) {
|
||||
@@ -206,4 +224,21 @@ public class AlarmStoreTest {
|
||||
});
|
||||
assertEquals(7, mAlarmStore.getNextDeliveryTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alarmClockRemovalListener() {
|
||||
final Runnable onRemoved = mock(Runnable.class);
|
||||
mAlarmStore.setAlarmClockRemovalListener(onRemoved);
|
||||
|
||||
final Alarm simpleAlarm = createAlarm(5, 0);
|
||||
final Alarm alarmClock = createAlarmClock(10);
|
||||
|
||||
addAlarmsToStore(simpleAlarm, alarmClock);
|
||||
|
||||
mAlarmStore.remove(simpleAlarm::equals);
|
||||
verifyZeroInteractions(onRemoved);
|
||||
|
||||
mAlarmStore.remove(alarmClock::equals);
|
||||
verify(onRemoved).run();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user