Loading apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java 0 → 100644 +211 −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 android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import android.app.AlarmManager; import android.app.IAlarmListener; import android.app.PendingIntent; import android.os.WorkSource; import android.util.IndentingPrintWriter; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; class Alarm { public final int type; public final long origWhen; public final boolean wakeup; public final PendingIntent operation; public final IAlarmListener listener; public final String listenerTag; public final String statsTag; public final WorkSource workSource; public final int flags; public final AlarmManager.AlarmClockInfo alarmClock; public final int uid; public final int creatorUid; public final String packageName; public final String sourcePackage; public int count; public long when; public long windowLength; public long whenElapsed; // 'when' in the elapsed time base public long maxWhenElapsed; // also in the elapsed time base // Expected alarm expiry time before app standby deferring is applied. public long expectedWhenElapsed; public long expectedMaxWhenElapsed; public long repeatInterval; public AlarmManagerService.PriorityClass priorityClass; Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag, WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info, int _uid, String _pkgName) { type = _type; origWhen = _when; wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP || _type == AlarmManager.RTC_WAKEUP; when = _when; whenElapsed = _whenElapsed; expectedWhenElapsed = _whenElapsed; windowLength = _windowLength; maxWhenElapsed = expectedMaxWhenElapsed = AlarmManagerService.clampPositive(_maxWhen); repeatInterval = _interval; operation = _op; listener = _rec; listenerTag = _listenerTag; statsTag = makeTag(_op, _listenerTag, _type); workSource = _ws; flags = _flags; alarmClock = _info; uid = _uid; packageName = _pkgName; sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; creatorUid = (operation != null) ? operation.getCreatorUid() : uid; } public static String makeTag(PendingIntent pi, String tag, int type) { final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP ? "*walarm*:" : "*alarm*:"; return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag); } public AlarmManagerService.WakeupEvent makeWakeupEvent(long nowRTC) { return new AlarmManagerService.WakeupEvent(nowRTC, creatorUid, (operation != null) ? operation.getIntent().getAction() : ("<listener>:" + listenerTag)); } // Returns true if either matches public boolean matches(PendingIntent pi, IAlarmListener rec) { return (operation != null) ? operation.equals(pi) : rec != null && listener.asBinder().equals(rec.asBinder()); } public boolean matches(String packageName) { return packageName.equals(sourcePackage); } @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Alarm{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" type "); sb.append(type); sb.append(" when "); sb.append(when); sb.append(" "); sb.append(" whenElapsed "); sb.append(whenElapsed); sb.append(" "); sb.append(sourcePackage); sb.append('}'); return sb.toString(); } /** * @deprecated Use {{@link #dump(IndentingPrintWriter, long, SimpleDateFormat)}} instead. */ @Deprecated public void dump(PrintWriter pw, String prefix, long nowELAPSED, SimpleDateFormat sdf) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, prefix, prefix); dump(ipw, nowELAPSED, sdf); } public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); ipw.print("tag="); ipw.println(statsTag); ipw.print("type="); ipw.print(type); ipw.print(" expectedWhenElapsed="); TimeUtils.formatDuration(expectedWhenElapsed, nowELAPSED, ipw); ipw.print(" expectedMaxWhenElapsed="); TimeUtils.formatDuration(expectedMaxWhenElapsed, nowELAPSED, ipw); ipw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed, nowELAPSED, ipw); ipw.print(" maxWhenElapsed="); TimeUtils.formatDuration(maxWhenElapsed, nowELAPSED, ipw); ipw.print(" when="); if (isRtc) { ipw.print(sdf.format(new Date(when))); } else { TimeUtils.formatDuration(when, nowELAPSED, ipw); } ipw.println(); ipw.print("window="); TimeUtils.formatDuration(windowLength, ipw); ipw.print(" repeatInterval="); ipw.print(repeatInterval); ipw.print(" count="); ipw.print(count); ipw.print(" flags=0x"); ipw.println(Integer.toHexString(flags)); if (alarmClock != null) { ipw.println("Alarm clock:"); ipw.print(" triggerTime="); ipw.println(sdf.format(new Date(alarmClock.getTriggerTime()))); ipw.print(" showIntent="); ipw.println(alarmClock.getShowIntent()); } ipw.print("operation="); ipw.println(operation); if (listener != null) { ipw.print("listener="); ipw.println(listener.asBinder()); } } public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { final long token = proto.start(fieldId); proto.write(AlarmProto.TAG, statsTag); proto.write(AlarmProto.TYPE, type); proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, whenElapsed - nowElapsed); proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength); proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval); proto.write(AlarmProto.COUNT, count); proto.write(AlarmProto.FLAGS, flags); if (alarmClock != null) { alarmClock.dumpDebug(proto, AlarmProto.ALARM_CLOCK); } if (operation != null) { operation.dumpDebug(proto, AlarmProto.OPERATION); } if (listener != null) { proto.write(AlarmProto.LISTENER, listener.asBinder().toString()); } proto.end(token); } } apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +515 −1065 File changed.Preview size limit exceeded, changes collapsed. Show changes apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java 0 → 100644 +127 −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 android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; import java.io.FileDescriptor; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.function.Predicate; /** * Used by {@link AlarmManagerService} to store alarms. * Besides basic add and remove operations, supports querying the next upcoming alarm times, * and all the alarms that are due at a given time. */ public interface AlarmStore { /** * Adds the given alarm. * * @param a The alarm to add. */ void add(Alarm a); /** * Removes alarms that pass the given predicate. * * @param whichAlarms The predicate describing the alarms to remove. * @return a list containing alarms that were removed. */ ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms); /** * Returns the total number of alarms in this store. */ int size(); /** * Get the next wakeup delivery time of all alarms in this store. * * @return a long timestamp in the {@link SystemClock#elapsedRealtime() elapsed} * timebase. */ long getNextWakeupDeliveryTime(); /** * Get the next delivery time of all alarms in this store. * * @return a long timestamp in the {@link SystemClock#elapsedRealtime() elapsed} * timebase. May or may not be the same as {{@link #getNextWakeupDeliveryTime()}}. */ long getNextDeliveryTime(); /** * Removes all alarms that are pending delivery at the given time. * * @param nowElapsed The time at which delivery eligibility is evaluated. * @return The list of alarms pending at the given time. */ ArrayList<Alarm> removePendingAlarms(long nowElapsed); /** * Adjusts alarm deliveries for all alarms according to the passed * {@link AlarmDeliveryCalculator} * * @return {@code true} if any of the alarm deliveries changed due to this call. */ boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator); /** * Returns all the alarms in the form of a list. */ ArrayList<Alarm> asList(); /** * Dumps the state of this alarm store into the passed print writer. Also accepts the current * timestamp and a {@link SimpleDateFormat} to format the timestamps as human readable delta * from the current time. * * Primary useful for debugging. Can be called from the * {@link android.os.Binder#dump(FileDescriptor PrintWriter, String[]) dump} method of the * caller. * @param ipw The {@link IndentingPrintWriter} to write to. * @param nowElapsed the time when the dump is requested in the * {@link SystemClock#elapsedRealtime() * elapsed} timebase. * @param sdf the date format to print timestamps in. */ void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf); /** * Dump the state of this alarm store as a proto buffer to the given stream. */ void dumpProto(ProtoOutputStream pos, long nowElapsed); /** * A functional interface used to update the alarm. Used to describe the update in * {@link #recalculateAlarmDeliveries(AlarmDeliveryCalculator)} */ @FunctionalInterface interface AlarmDeliveryCalculator { /** * Updates the given alarm's delivery time. * * @param a the alarm to update. * @return {@code true} if any change was made, {@code false} otherwise. */ boolean updateAlarmDelivery(Alarm a); } } apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java 0 → 100644 +379 −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.DEBUG_BATCH; import static com.android.server.alarm.AlarmManagerService.TAG; import static com.android.server.alarm.AlarmManagerService.clampPositive; 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; /** * Batching implementation of an Alarm Store. * This keeps the alarms in batches, which are sorted on the start time of their delivery window. */ public class BatchingAlarmStore implements AlarmStore { private ArrayList<Batch> mAlarmBatches = new ArrayList<>(); private int mSize; private AlarmClockRemovalListener mAlarmClockRemovalListener; interface Stats { int REBATCH_ALL_ALARMS = 1; } final StatLogger mStatLogger = new StatLogger("Alarm store 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.whenElapsed; long when2 = a2.whenElapsed; if (when1 > when2) { return 1; } if (when1 < when2) { return -1; } return 0; }; BatchingAlarmStore(AlarmClockRemovalListener listener) { mAlarmClockRemovalListener = listener; } @Override public void add(Alarm a) { insertAndBatchAlarm(a); mSize++; } @Override public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) { final ArrayList<Alarm> removed = new ArrayList<>(); for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { final Batch b = mAlarmBatches.get(i); removed.addAll(b.remove(whichAlarms)); if (b.size() == 0) { mAlarmBatches.remove(i); } } if (!removed.isEmpty()) { mSize -= removed.size(); rebatchAllAlarms(); } return removed; } private void rebatchAllAlarms() { final long start = mStatLogger.getTime(); final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone(); mAlarmBatches.clear(); for (final Batch batch : oldBatches) { for (int i = 0; i < batch.size(); i++) { insertAndBatchAlarm(batch.get(i)); } } mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start); } @Override public int size() { return mSize; } @Override public long getNextWakeupDeliveryTime() { for (Batch b : mAlarmBatches) { if (b.hasWakeups()) { return b.mStart; } } return 0; } @Override public long getNextDeliveryTime() { if (mAlarmBatches.size() > 0) { return mAlarmBatches.get(0).mStart; } return 0; } @Override public ArrayList<Alarm> removePendingAlarms(long nowElapsed) { final ArrayList<Alarm> removedAlarms = new ArrayList<>(); while (mAlarmBatches.size() > 0) { final Batch batch = mAlarmBatches.get(0); if (batch.mStart > nowElapsed) { break; } mAlarmBatches.remove(0); for (int i = 0; i < batch.size(); i++) { removedAlarms.add(batch.get(i)); } } mSize -= removedAlarms.size(); return removedAlarms; } @Override public boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) { boolean changed = false; for (final Batch b : mAlarmBatches) { for (int i = 0; i < b.size(); i++) { changed |= deliveryCalculator.updateAlarmDelivery(b.get(i)); } } if (changed) { rebatchAllAlarms(); } return changed; } @Override public ArrayList<Alarm> asList() { final ArrayList<Alarm> allAlarms = new ArrayList<>(); for (final Batch batch : mAlarmBatches) { for (int i = 0; i < batch.size(); i++) { allAlarms.add(batch.get(i)); } } return allAlarms; } @Override public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) { ipw.print("Pending alarm batches: "); ipw.println(mAlarmBatches.size()); for (Batch b : mAlarmBatches) { ipw.print(b); ipw.println(':'); ipw.increaseIndent(); dumpAlarmList(ipw, b.mAlarms, nowElapsed, sdf); ipw.decreaseIndent(); } mStatLogger.dump(ipw); } @Override public void dumpProto(ProtoOutputStream pos, long nowElapsed) { for (Batch b : mAlarmBatches) { b.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARM_BATCHES, nowElapsed); } } private void insertAndBatchAlarm(Alarm alarm) { final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesce(alarm.whenElapsed, alarm.maxWhenElapsed); if (whichBatch < 0) { addBatch(mAlarmBatches, new Batch(alarm)); } else { final Batch batch = mAlarmBatches.get(whichBatch); if (batch.add(alarm)) { // The start time of this batch advanced, so batch ordering may // have just been broken. Move it to where it now belongs. mAlarmBatches.remove(whichBatch); addBatch(mAlarmBatches, batch); } } } static void addBatch(ArrayList<Batch> list, Batch newBatch) { int index = Collections.binarySearch(list, newBatch, sBatchOrder); if (index < 0) { index = 0 - index - 1; } list.add(index, newBatch); } // Return the index of the matching batch, or -1 if none found. private int attemptCoalesce(long whenElapsed, long maxWhen) { final int n = mAlarmBatches.size(); for (int i = 0; i < n; i++) { Batch b = mAlarmBatches.get(i); if ((b.mFlags & AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) { return i; } } return -1; } final class Batch { long mStart; // These endpoints are always in ELAPSED long mEnd; int mFlags; // Flags for alarms, such as FLAG_STANDALONE. final ArrayList<Alarm> mAlarms = new ArrayList<>(); Batch(Alarm seed) { mStart = seed.whenElapsed; mEnd = clampPositive(seed.maxWhenElapsed); mFlags = seed.flags; mAlarms.add(seed); } int size() { return mAlarms.size(); } Alarm get(int index) { return mAlarms.get(index); } boolean canHold(long whenElapsed, long maxWhen) { return (mEnd >= whenElapsed) && (mStart <= maxWhen); } boolean add(Alarm alarm) { boolean newStart = false; // narrows the batch if necessary; presumes that canHold(alarm) is true int index = Collections.binarySearch(mAlarms, alarm, sIncreasingTimeOrder); if (index < 0) { index = 0 - index - 1; } mAlarms.add(index, alarm); if (DEBUG_BATCH) { Slog.v(TAG, "Adding " + alarm + " to " + this); } if (alarm.whenElapsed > mStart) { mStart = alarm.whenElapsed; newStart = true; } if (alarm.maxWhenElapsed < mEnd) { mEnd = alarm.maxWhenElapsed; } mFlags |= alarm.flags; if (DEBUG_BATCH) { Slog.v(TAG, " => now " + this); } return newStart; } ArrayList<Alarm> remove(Predicate<Alarm> predicate) { final ArrayList<Alarm> removed = new ArrayList<>(); long newStart = 0; // recalculate endpoints as we go long newEnd = Long.MAX_VALUE; int newFlags = 0; for (int i = 0; i < mAlarms.size(); ) { Alarm alarm = mAlarms.get(i); if (predicate.test(alarm)) { removed.add(mAlarms.remove(i)); if (alarm.alarmClock != null && mAlarmClockRemovalListener != null) { mAlarmClockRemovalListener.onRemoved(); } if (isTimeTickAlarm(alarm)) { // 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"); } } else { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; } if (alarm.maxWhenElapsed < newEnd) { newEnd = alarm.maxWhenElapsed; } newFlags |= alarm.flags; i++; } } if (!removed.isEmpty()) { // commit the new batch bounds mStart = newStart; mEnd = newEnd; mFlags = newFlags; } return removed; } boolean hasWakeups() { final int n = mAlarms.size(); for (int i = 0; i < n; i++) { Alarm a = mAlarms.get(i); if (a.wakeup) { return true; } } return false; } @Override public String toString() { StringBuilder b = new StringBuilder(40); b.append("Batch{"); b.append(Integer.toHexString(this.hashCode())); b.append(" num="); b.append(size()); b.append(" start="); b.append(mStart); b.append(" end="); b.append(mEnd); if (mFlags != 0) { b.append(" flgs=0x"); b.append(Integer.toHexString(mFlags)); } b.append('}'); return b.toString(); } public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { final long token = proto.start(fieldId); proto.write(BatchProto.START_REALTIME, mStart); proto.write(BatchProto.END_REALTIME, mEnd); proto.write(BatchProto.FLAGS, mFlags); for (Alarm a : mAlarms) { a.dumpDebug(proto, BatchProto.ALARMS, nowElapsed); } proto.end(token); } } @FunctionalInterface interface AlarmClockRemovalListener { void onRemoved(); } } core/java/com/android/internal/util/StatLogger.java +12 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.internal.util; import android.os.SystemClock; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.proto.ProtoOutputStream; Loading Loading @@ -65,8 +66,14 @@ public class StatLogger { private long mNextTickTime = SystemClock.elapsedRealtime() + 1000; private final String[] mLabels; private final String mStatsTag; public StatLogger(String[] eventLabels) { this(null, eventLabels); } public StatLogger(String statsTag, String[] eventLabels) { mStatsTag = statsTag; SIZE = eventLabels.length; mCountStats = new int[SIZE]; mDurationStats = new long[SIZE]; Loading Loading @@ -135,7 +142,11 @@ public class StatLogger { public void dump(IndentingPrintWriter pw) { synchronized (mLock) { if (!TextUtils.isEmpty(mStatsTag)) { pw.println(mStatsTag + ":"); } else { pw.println("Stats:"); } pw.increaseIndent(); for (int i = 0; i < SIZE; i++) { final int count = mCountStats[i]; Loading Loading
apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java 0 → 100644 +211 −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 android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import android.app.AlarmManager; import android.app.IAlarmListener; import android.app.PendingIntent; import android.os.WorkSource; import android.util.IndentingPrintWriter; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; class Alarm { public final int type; public final long origWhen; public final boolean wakeup; public final PendingIntent operation; public final IAlarmListener listener; public final String listenerTag; public final String statsTag; public final WorkSource workSource; public final int flags; public final AlarmManager.AlarmClockInfo alarmClock; public final int uid; public final int creatorUid; public final String packageName; public final String sourcePackage; public int count; public long when; public long windowLength; public long whenElapsed; // 'when' in the elapsed time base public long maxWhenElapsed; // also in the elapsed time base // Expected alarm expiry time before app standby deferring is applied. public long expectedWhenElapsed; public long expectedMaxWhenElapsed; public long repeatInterval; public AlarmManagerService.PriorityClass priorityClass; Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag, WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info, int _uid, String _pkgName) { type = _type; origWhen = _when; wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP || _type == AlarmManager.RTC_WAKEUP; when = _when; whenElapsed = _whenElapsed; expectedWhenElapsed = _whenElapsed; windowLength = _windowLength; maxWhenElapsed = expectedMaxWhenElapsed = AlarmManagerService.clampPositive(_maxWhen); repeatInterval = _interval; operation = _op; listener = _rec; listenerTag = _listenerTag; statsTag = makeTag(_op, _listenerTag, _type); workSource = _ws; flags = _flags; alarmClock = _info; uid = _uid; packageName = _pkgName; sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; creatorUid = (operation != null) ? operation.getCreatorUid() : uid; } public static String makeTag(PendingIntent pi, String tag, int type) { final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP ? "*walarm*:" : "*alarm*:"; return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag); } public AlarmManagerService.WakeupEvent makeWakeupEvent(long nowRTC) { return new AlarmManagerService.WakeupEvent(nowRTC, creatorUid, (operation != null) ? operation.getIntent().getAction() : ("<listener>:" + listenerTag)); } // Returns true if either matches public boolean matches(PendingIntent pi, IAlarmListener rec) { return (operation != null) ? operation.equals(pi) : rec != null && listener.asBinder().equals(rec.asBinder()); } public boolean matches(String packageName) { return packageName.equals(sourcePackage); } @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Alarm{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" type "); sb.append(type); sb.append(" when "); sb.append(when); sb.append(" "); sb.append(" whenElapsed "); sb.append(whenElapsed); sb.append(" "); sb.append(sourcePackage); sb.append('}'); return sb.toString(); } /** * @deprecated Use {{@link #dump(IndentingPrintWriter, long, SimpleDateFormat)}} instead. */ @Deprecated public void dump(PrintWriter pw, String prefix, long nowELAPSED, SimpleDateFormat sdf) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, prefix, prefix); dump(ipw, nowELAPSED, sdf); } public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); ipw.print("tag="); ipw.println(statsTag); ipw.print("type="); ipw.print(type); ipw.print(" expectedWhenElapsed="); TimeUtils.formatDuration(expectedWhenElapsed, nowELAPSED, ipw); ipw.print(" expectedMaxWhenElapsed="); TimeUtils.formatDuration(expectedMaxWhenElapsed, nowELAPSED, ipw); ipw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed, nowELAPSED, ipw); ipw.print(" maxWhenElapsed="); TimeUtils.formatDuration(maxWhenElapsed, nowELAPSED, ipw); ipw.print(" when="); if (isRtc) { ipw.print(sdf.format(new Date(when))); } else { TimeUtils.formatDuration(when, nowELAPSED, ipw); } ipw.println(); ipw.print("window="); TimeUtils.formatDuration(windowLength, ipw); ipw.print(" repeatInterval="); ipw.print(repeatInterval); ipw.print(" count="); ipw.print(count); ipw.print(" flags=0x"); ipw.println(Integer.toHexString(flags)); if (alarmClock != null) { ipw.println("Alarm clock:"); ipw.print(" triggerTime="); ipw.println(sdf.format(new Date(alarmClock.getTriggerTime()))); ipw.print(" showIntent="); ipw.println(alarmClock.getShowIntent()); } ipw.print("operation="); ipw.println(operation); if (listener != null) { ipw.print("listener="); ipw.println(listener.asBinder()); } } public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { final long token = proto.start(fieldId); proto.write(AlarmProto.TAG, statsTag); proto.write(AlarmProto.TYPE, type); proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, whenElapsed - nowElapsed); proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength); proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval); proto.write(AlarmProto.COUNT, count); proto.write(AlarmProto.FLAGS, flags); if (alarmClock != null) { alarmClock.dumpDebug(proto, AlarmProto.ALARM_CLOCK); } if (operation != null) { operation.dumpDebug(proto, AlarmProto.OPERATION); } if (listener != null) { proto.write(AlarmProto.LISTENER, listener.asBinder().toString()); } proto.end(token); } }
apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +515 −1065 File changed.Preview size limit exceeded, changes collapsed. Show changes
apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java 0 → 100644 +127 −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 android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; import java.io.FileDescriptor; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.function.Predicate; /** * Used by {@link AlarmManagerService} to store alarms. * Besides basic add and remove operations, supports querying the next upcoming alarm times, * and all the alarms that are due at a given time. */ public interface AlarmStore { /** * Adds the given alarm. * * @param a The alarm to add. */ void add(Alarm a); /** * Removes alarms that pass the given predicate. * * @param whichAlarms The predicate describing the alarms to remove. * @return a list containing alarms that were removed. */ ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms); /** * Returns the total number of alarms in this store. */ int size(); /** * Get the next wakeup delivery time of all alarms in this store. * * @return a long timestamp in the {@link SystemClock#elapsedRealtime() elapsed} * timebase. */ long getNextWakeupDeliveryTime(); /** * Get the next delivery time of all alarms in this store. * * @return a long timestamp in the {@link SystemClock#elapsedRealtime() elapsed} * timebase. May or may not be the same as {{@link #getNextWakeupDeliveryTime()}}. */ long getNextDeliveryTime(); /** * Removes all alarms that are pending delivery at the given time. * * @param nowElapsed The time at which delivery eligibility is evaluated. * @return The list of alarms pending at the given time. */ ArrayList<Alarm> removePendingAlarms(long nowElapsed); /** * Adjusts alarm deliveries for all alarms according to the passed * {@link AlarmDeliveryCalculator} * * @return {@code true} if any of the alarm deliveries changed due to this call. */ boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator); /** * Returns all the alarms in the form of a list. */ ArrayList<Alarm> asList(); /** * Dumps the state of this alarm store into the passed print writer. Also accepts the current * timestamp and a {@link SimpleDateFormat} to format the timestamps as human readable delta * from the current time. * * Primary useful for debugging. Can be called from the * {@link android.os.Binder#dump(FileDescriptor PrintWriter, String[]) dump} method of the * caller. * @param ipw The {@link IndentingPrintWriter} to write to. * @param nowElapsed the time when the dump is requested in the * {@link SystemClock#elapsedRealtime() * elapsed} timebase. * @param sdf the date format to print timestamps in. */ void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf); /** * Dump the state of this alarm store as a proto buffer to the given stream. */ void dumpProto(ProtoOutputStream pos, long nowElapsed); /** * A functional interface used to update the alarm. Used to describe the update in * {@link #recalculateAlarmDeliveries(AlarmDeliveryCalculator)} */ @FunctionalInterface interface AlarmDeliveryCalculator { /** * Updates the given alarm's delivery time. * * @param a the alarm to update. * @return {@code true} if any change was made, {@code false} otherwise. */ boolean updateAlarmDelivery(Alarm a); } }
apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java 0 → 100644 +379 −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.DEBUG_BATCH; import static com.android.server.alarm.AlarmManagerService.TAG; import static com.android.server.alarm.AlarmManagerService.clampPositive; 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; /** * Batching implementation of an Alarm Store. * This keeps the alarms in batches, which are sorted on the start time of their delivery window. */ public class BatchingAlarmStore implements AlarmStore { private ArrayList<Batch> mAlarmBatches = new ArrayList<>(); private int mSize; private AlarmClockRemovalListener mAlarmClockRemovalListener; interface Stats { int REBATCH_ALL_ALARMS = 1; } final StatLogger mStatLogger = new StatLogger("Alarm store 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.whenElapsed; long when2 = a2.whenElapsed; if (when1 > when2) { return 1; } if (when1 < when2) { return -1; } return 0; }; BatchingAlarmStore(AlarmClockRemovalListener listener) { mAlarmClockRemovalListener = listener; } @Override public void add(Alarm a) { insertAndBatchAlarm(a); mSize++; } @Override public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) { final ArrayList<Alarm> removed = new ArrayList<>(); for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { final Batch b = mAlarmBatches.get(i); removed.addAll(b.remove(whichAlarms)); if (b.size() == 0) { mAlarmBatches.remove(i); } } if (!removed.isEmpty()) { mSize -= removed.size(); rebatchAllAlarms(); } return removed; } private void rebatchAllAlarms() { final long start = mStatLogger.getTime(); final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone(); mAlarmBatches.clear(); for (final Batch batch : oldBatches) { for (int i = 0; i < batch.size(); i++) { insertAndBatchAlarm(batch.get(i)); } } mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start); } @Override public int size() { return mSize; } @Override public long getNextWakeupDeliveryTime() { for (Batch b : mAlarmBatches) { if (b.hasWakeups()) { return b.mStart; } } return 0; } @Override public long getNextDeliveryTime() { if (mAlarmBatches.size() > 0) { return mAlarmBatches.get(0).mStart; } return 0; } @Override public ArrayList<Alarm> removePendingAlarms(long nowElapsed) { final ArrayList<Alarm> removedAlarms = new ArrayList<>(); while (mAlarmBatches.size() > 0) { final Batch batch = mAlarmBatches.get(0); if (batch.mStart > nowElapsed) { break; } mAlarmBatches.remove(0); for (int i = 0; i < batch.size(); i++) { removedAlarms.add(batch.get(i)); } } mSize -= removedAlarms.size(); return removedAlarms; } @Override public boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) { boolean changed = false; for (final Batch b : mAlarmBatches) { for (int i = 0; i < b.size(); i++) { changed |= deliveryCalculator.updateAlarmDelivery(b.get(i)); } } if (changed) { rebatchAllAlarms(); } return changed; } @Override public ArrayList<Alarm> asList() { final ArrayList<Alarm> allAlarms = new ArrayList<>(); for (final Batch batch : mAlarmBatches) { for (int i = 0; i < batch.size(); i++) { allAlarms.add(batch.get(i)); } } return allAlarms; } @Override public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) { ipw.print("Pending alarm batches: "); ipw.println(mAlarmBatches.size()); for (Batch b : mAlarmBatches) { ipw.print(b); ipw.println(':'); ipw.increaseIndent(); dumpAlarmList(ipw, b.mAlarms, nowElapsed, sdf); ipw.decreaseIndent(); } mStatLogger.dump(ipw); } @Override public void dumpProto(ProtoOutputStream pos, long nowElapsed) { for (Batch b : mAlarmBatches) { b.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARM_BATCHES, nowElapsed); } } private void insertAndBatchAlarm(Alarm alarm) { final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesce(alarm.whenElapsed, alarm.maxWhenElapsed); if (whichBatch < 0) { addBatch(mAlarmBatches, new Batch(alarm)); } else { final Batch batch = mAlarmBatches.get(whichBatch); if (batch.add(alarm)) { // The start time of this batch advanced, so batch ordering may // have just been broken. Move it to where it now belongs. mAlarmBatches.remove(whichBatch); addBatch(mAlarmBatches, batch); } } } static void addBatch(ArrayList<Batch> list, Batch newBatch) { int index = Collections.binarySearch(list, newBatch, sBatchOrder); if (index < 0) { index = 0 - index - 1; } list.add(index, newBatch); } // Return the index of the matching batch, or -1 if none found. private int attemptCoalesce(long whenElapsed, long maxWhen) { final int n = mAlarmBatches.size(); for (int i = 0; i < n; i++) { Batch b = mAlarmBatches.get(i); if ((b.mFlags & AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) { return i; } } return -1; } final class Batch { long mStart; // These endpoints are always in ELAPSED long mEnd; int mFlags; // Flags for alarms, such as FLAG_STANDALONE. final ArrayList<Alarm> mAlarms = new ArrayList<>(); Batch(Alarm seed) { mStart = seed.whenElapsed; mEnd = clampPositive(seed.maxWhenElapsed); mFlags = seed.flags; mAlarms.add(seed); } int size() { return mAlarms.size(); } Alarm get(int index) { return mAlarms.get(index); } boolean canHold(long whenElapsed, long maxWhen) { return (mEnd >= whenElapsed) && (mStart <= maxWhen); } boolean add(Alarm alarm) { boolean newStart = false; // narrows the batch if necessary; presumes that canHold(alarm) is true int index = Collections.binarySearch(mAlarms, alarm, sIncreasingTimeOrder); if (index < 0) { index = 0 - index - 1; } mAlarms.add(index, alarm); if (DEBUG_BATCH) { Slog.v(TAG, "Adding " + alarm + " to " + this); } if (alarm.whenElapsed > mStart) { mStart = alarm.whenElapsed; newStart = true; } if (alarm.maxWhenElapsed < mEnd) { mEnd = alarm.maxWhenElapsed; } mFlags |= alarm.flags; if (DEBUG_BATCH) { Slog.v(TAG, " => now " + this); } return newStart; } ArrayList<Alarm> remove(Predicate<Alarm> predicate) { final ArrayList<Alarm> removed = new ArrayList<>(); long newStart = 0; // recalculate endpoints as we go long newEnd = Long.MAX_VALUE; int newFlags = 0; for (int i = 0; i < mAlarms.size(); ) { Alarm alarm = mAlarms.get(i); if (predicate.test(alarm)) { removed.add(mAlarms.remove(i)); if (alarm.alarmClock != null && mAlarmClockRemovalListener != null) { mAlarmClockRemovalListener.onRemoved(); } if (isTimeTickAlarm(alarm)) { // 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"); } } else { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; } if (alarm.maxWhenElapsed < newEnd) { newEnd = alarm.maxWhenElapsed; } newFlags |= alarm.flags; i++; } } if (!removed.isEmpty()) { // commit the new batch bounds mStart = newStart; mEnd = newEnd; mFlags = newFlags; } return removed; } boolean hasWakeups() { final int n = mAlarms.size(); for (int i = 0; i < n; i++) { Alarm a = mAlarms.get(i); if (a.wakeup) { return true; } } return false; } @Override public String toString() { StringBuilder b = new StringBuilder(40); b.append("Batch{"); b.append(Integer.toHexString(this.hashCode())); b.append(" num="); b.append(size()); b.append(" start="); b.append(mStart); b.append(" end="); b.append(mEnd); if (mFlags != 0) { b.append(" flgs=0x"); b.append(Integer.toHexString(mFlags)); } b.append('}'); return b.toString(); } public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { final long token = proto.start(fieldId); proto.write(BatchProto.START_REALTIME, mStart); proto.write(BatchProto.END_REALTIME, mEnd); proto.write(BatchProto.FLAGS, mFlags); for (Alarm a : mAlarms) { a.dumpDebug(proto, BatchProto.ALARMS, nowElapsed); } proto.end(token); } } @FunctionalInterface interface AlarmClockRemovalListener { void onRemoved(); } }
core/java/com/android/internal/util/StatLogger.java +12 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.internal.util; import android.os.SystemClock; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.proto.ProtoOutputStream; Loading Loading @@ -65,8 +66,14 @@ public class StatLogger { private long mNextTickTime = SystemClock.elapsedRealtime() + 1000; private final String[] mLabels; private final String mStatsTag; public StatLogger(String[] eventLabels) { this(null, eventLabels); } public StatLogger(String statsTag, String[] eventLabels) { mStatsTag = statsTag; SIZE = eventLabels.length; mCountStats = new int[SIZE]; mDurationStats = new long[SIZE]; Loading Loading @@ -135,7 +142,11 @@ public class StatLogger { public void dump(IndentingPrintWriter pw) { synchronized (mLock) { if (!TextUtils.isEmpty(mStatsTag)) { pw.println(mStatsTag + ":"); } else { pw.println("Stats:"); } pw.increaseIndent(); for (int i = 0; i < SIZE; i++) { final int count = mCountStats[i]; Loading