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

Commit 1e19bfb9 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introducing an interface to store alarms"

parents a23fe197 323fccc6
Loading
Loading
Loading
Loading
+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);
    }
}
+515 −1065

File changed.

Preview size limit exceeded, changes collapsed.

+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);
    }
}
+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();
    }
}
+12 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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];
@@ -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