Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 323fccc6 authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Introducing an interface to store alarms

The alarms inside alarm manager service are stored in a variety of
different lists, the main one being an array list of Batch objects. Over
time due to various policy changes, it has caused a lot of coupling
between the service's business logic and the management of these data
structures.

This change introduces an interface to store alarms (a.k.a. AlarmStore)
and attempts to create a well defined boundary across which the service
interacts with this data structure. The interface presently supports
the following operations:
- add: to add an alarm.
- remove: to remove any alarms described by a predicate.
- removePendingAlarms: to remove any alarms that are due at a given
time.
- getNextDeliveryTime: the next upcoming alarm's delivery time.
- getNextWakeupDeliveryTime: the next upcoming wakeup alarm's delivery
time.
- recalculateAlarmDeliveries: to adjust the alarm deliveries in a way
described by the caller.
All the times are interpreted in the elapsed timebase.

The details of how the alarms are stored and managed, such as their
relative ordering and the complexity of each of the supported operations
is left to the implementations to decide. The expectation is that
this will make it easier to change and optimize these aspects of the
data structure in the future, while also making it simpler to make
changes to the service side policies.

Apart from the above, some formatting was corrected, obsolete or dead
code was removed and a bug where we were missing a rebatch was fixed.

Test: atest FrameworksMockingServicesTests:com.android.server.alarm
atest CtsAlarmManagerTestCases

Bug: 161497385
Change-Id: I8ac475d6c6a827500bf93be982cd40897f6743ae
parent 4a53edcf
Loading
Loading
Loading
Loading
+211 −0
Original line number Original line 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);
    }
}
+515 −1065

File changed.

Preview size limit exceeded, changes collapsed.

+127 −0
Original line number Original line 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);
    }
}
+379 −0
Original line number Original line 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();
    }
}
+12 −1
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.internal.util;
package com.android.internal.util;


import android.os.SystemClock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoOutputStream;
@@ -65,8 +66,14 @@ public class StatLogger {
    private long mNextTickTime = SystemClock.elapsedRealtime() + 1000;
    private long mNextTickTime = SystemClock.elapsedRealtime() + 1000;


    private final String[] mLabels;
    private final String[] mLabels;
    private final String mStatsTag;


    public StatLogger(String[] eventLabels) {
    public StatLogger(String[] eventLabels) {
        this(null, eventLabels);
    }

    public StatLogger(String statsTag, String[] eventLabels) {
        mStatsTag = statsTag;
        SIZE = eventLabels.length;
        SIZE = eventLabels.length;
        mCountStats = new int[SIZE];
        mCountStats = new int[SIZE];
        mDurationStats = new long[SIZE];
        mDurationStats = new long[SIZE];
@@ -135,7 +142,11 @@ public class StatLogger {


    public void dump(IndentingPrintWriter pw) {
    public void dump(IndentingPrintWriter pw) {
        synchronized (mLock) {
        synchronized (mLock) {
            if (!TextUtils.isEmpty(mStatsTag)) {
                pw.println(mStatsTag + ":");
            } else {
                pw.println("Stats:");
                pw.println("Stats:");
            }
            pw.increaseIndent();
            pw.increaseIndent();
            for (int i = 0; i < SIZE; i++) {
            for (int i = 0; i < SIZE; i++) {
                final int count = mCountStats[i];
                final int count = mCountStats[i];
Loading