Loading apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +40 −4 Original line number Diff line number Diff line Loading @@ -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 = Loading @@ -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<>(); Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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 Loading @@ -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. Loading Loading @@ -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(); } Loading Loading @@ -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. Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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(); Loading apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java +14 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -47,6 +54,12 @@ 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()}. Loading apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java +23 −36 Original line number Diff line number Diff line Loading @@ -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<Alarm> sIncreasingTimeOrder = (a1, a2) -> { long when1 = a1.getWhenElapsed(); long when2 = a2.getWhenElapsed(); if (when1 > when2) { return 1; } if (when1 < when2) { return -1; } return 0; }; private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart); BatchingAlarmStore(AlarmClockRemovalListener listener) { mAlarmClockRemovalListener = listener; } private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong( Alarm::getWhenElapsed); @Override public void add(Alarm a) { Loading @@ -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<>(); Loading @@ -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) { Loading Loading @@ -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 Loading Loading @@ -388,9 +380,4 @@ public class BatchingAlarmStore implements AlarmStore { proto.end(token); } } @FunctionalInterface interface AlarmClockRemovalListener { void onRemoved(); } } apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java 0 → 100644 +217 −0 Original line number Diff line number Diff line /* * 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); } } } core/proto/android/server/alarm/alarmmanagerservice.proto +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +40 −4 Original line number Diff line number Diff line Loading @@ -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 = Loading @@ -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<>(); Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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 Loading @@ -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. Loading Loading @@ -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(); } Loading Loading @@ -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. Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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(); Loading
apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java +14 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -47,6 +54,12 @@ 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()}. Loading
apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java +23 −36 Original line number Diff line number Diff line Loading @@ -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<Alarm> sIncreasingTimeOrder = (a1, a2) -> { long when1 = a1.getWhenElapsed(); long when2 = a2.getWhenElapsed(); if (when1 > when2) { return 1; } if (when1 < when2) { return -1; } return 0; }; private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart); BatchingAlarmStore(AlarmClockRemovalListener listener) { mAlarmClockRemovalListener = listener; } private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong( Alarm::getWhenElapsed); @Override public void add(Alarm a) { Loading @@ -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<>(); Loading @@ -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) { Loading Loading @@ -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 Loading Loading @@ -388,9 +380,4 @@ public class BatchingAlarmStore implements AlarmStore { proto.end(token); } } @FunctionalInterface interface AlarmClockRemovalListener { void onRemoved(); } }
apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java 0 → 100644 +217 −0 Original line number Diff line number Diff line /* * 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); } } }
core/proto/android/server/alarm/alarmmanagerservice.proto +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading