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

Commit f7fc099c authored by Mohamad Mahmoud's avatar Mohamad Mahmoud
Browse files

Enable pre-ANR method tracing via AnrTimer split point notifications

Adds support for triggering method tracing shortly before ANR expiry using AnrTimer split points, enabling proactive pre-anr notifications.

- Introduces split point support in AnrTimer, allowing early actions (trace, notify, expire) at specific percentages of timer duration.
- Adds a predefined 50% split point (`TOKEN_LONG_METHOD_TRACING`) used to trigger `LongMethodTracer` before the ANR.
- Extends `AnrTimer.Args` to configure long method tracing, controlled via the flag.
- Updates the native AnrTimerService to support multiple split points per timer with associated actions.
- Adds plumbing across Java, JNI and native, enabling early notifications to the Java layer via `notifyEarly`.

This enables triggering method traces before a timer-based ANR is triggered, enabling LMT for  Service and Broadcast timeouts.

Design Document: go/long-method-tracing-for-anrs
Test: manually tested with Services and Broadcasts, atest AnrTimerTest
Bug: 419753987
Flag: com.android.server.am.enable_long_method_tracing_on_anr_timer

Change-Id: I0a8b3518e1df54e3f5dfd73dfd91f9357d8ec495
parent 176736ca
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -785,13 +785,17 @@ public final class ActiveServices {
        this.mActiveServiceAnrTimer = new ProcessAnrTimer(service,
                ActivityManagerService.SERVICE_TIMEOUT_MSG,
                "SERVICE_TIMEOUT",
                new AnrTimer.Args());
                new AnrTimer.Args().longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
        this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
                ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
                "SHORT_FGS_TIMEOUT");
                "SHORT_FGS_TIMEOUT",
                new AnrTimer.Args().longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
        this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                "SERVICE_FOREGROUND_TIMEOUT", new AnrTimer.Args().extend(true));
                "SERVICE_FOREGROUND_TIMEOUT",
                new AnrTimer.Args()
                            .extend(true)
                            .longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
    }

    void systemServicesReady() {
+3 −1
Original line number Diff line number Diff line
@@ -1312,7 +1312,9 @@ class BroadcastQueueImpl extends BroadcastQueue {
        BroadcastAnrTimer(@NonNull Handler handler) {
            super(Objects.requireNonNull(handler),
                    MSG_DELIVERY_TIMEOUT, "BROADCAST_TIMEOUT",
                    new AnrTimer.Args().extend(true));
                    new AnrTimer.Args()
                            .extend(true)
                            .longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
        }

        @Override
+8 −0
Original line number Diff line number Diff line
@@ -279,3 +279,11 @@ flag {
    description: "Allow am to dump information and contents of bitmaps"
    bug: "369619160"
}

flag {
    name: "enable_long_method_tracing_on_anr_timer"
    namespace: "stability"
    description: "Enable Long Method Tracing for ANR Timer"
    bug: "419753987"
    is_fixed_read_only: true
}
+127 −27
Original line number Diff line number Diff line
@@ -18,13 +18,14 @@ package com.android.server.utils;

import static android.text.TextUtils.formatSimple;

import static java.util.Comparator.comparingInt;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.os.Trace;
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.CloseGuard;
@@ -39,12 +40,14 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.RingBuffer;

import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.StringJoiner;
import java.util.TreeSet;

/**
 * This class managers AnrTimers.  An AnrTimer is a substitute for a delayed Message.  In legacy
@@ -146,11 +149,28 @@ public class AnrTimer<V> implements AutoCloseable {
    /** The default injector. */
    private static final Injector sDefaultInjector = new Injector();

    /**
     * Token for Long Method Tracing notifications.
     * This token is used in early notifications to trigger long method tracing.
     */
    private static final int TOKEN_LONG_METHOD_TRACING = 0x4d54;

    /** Minimum duration to trace long methods */
    private static final int MIN_LMT_DURATION_MS = 1000;

    /**
     * This class provides build-style arguments to an AnrTimer constructor.  This simplifies the
     * number of AnrTimer constructors needed, especially as new options are added.
     */
    public static class Args {

        /** Represents a point in time (as percent of total) and an associated token. */
        private record SplitPoint(int percent, int token) {}

        /** Split point for long method tracing, at 50% elapsed time. */
        private static final SplitPoint sLongMethodTracingPoint =
                new SplitPoint(50, TOKEN_LONG_METHOD_TRACING);

        /** The Injector (used only for testing). */
        private Injector mInjector = AnrTimer.sDefaultInjector;

@@ -160,6 +180,19 @@ public class AnrTimer<V> implements AutoCloseable {
        /** Grant timer extensions when the system is heavily loaded. */
        private boolean mExtend = false;

        /**
         * All split points, each specifying a percent threshold and an associated token.
         *
         * This set is sorted by percent first so the collection is ordered the way we
         * want, then token second so tow split points with the same percent do not collide.
         *
         * A TreeSet is used to maintain this sorted order, and the uniqueness of split points.
         *
         */
        private final SortedSet<SplitPoint> mSplitPoints =
                new TreeSet<>(comparingInt(SplitPoint::percent)
                        .thenComparingInt(SplitPoint::token));

        // This is only used for testing, so it is limited to package visibility.
        Args injector(@NonNull Injector injector) {
            mInjector = injector;
@@ -175,6 +208,62 @@ public class AnrTimer<V> implements AutoCloseable {
            mExtend = flag;
            return this;
        }

        /**
         * Enables or disables long method tracing.
         * When enabled, the timer will trigger long method tracing if it reaches 50%
         * of its timeout duration.
         *
         * @param enabled {@code true} to enable long method tracing; {@code false} to disable it.
         * @return this {@link Args} instance for chaining.
         */
        public Args longMethodTracing(boolean enabled) {
            final int percent = 50;
            if (enabled) {
                mSplitPoints.add(sLongMethodTracingPoint);
            } else {
                mSplitPoints.remove(sLongMethodTracingPoint);
            }
            return this;

        }

        /**
         * Extracts the percent values from all {@code SplitPoint} objects into an array.
         * <p>
         * This method creates an integer array containing the percent value
         * from each {@code SplitPoint} in the same order they appear in the original list.
         *
         * @return A new {@code int[]} array of all percent values. Never returns null.
         * @see SplitPoint#percent()
         */
        public int[] getSplitPercentArray() {
            int[] percents = new int[mSplitPoints.size()];
            int i = 0;
            for (SplitPoint sp : mSplitPoints) {
                percents[i++] = sp.percent();
            }
            return percents;
        }

        /**
         * Extracts the token values from all {@code SplitPoint} objects into an array.
         * <p>
         * This method creates an integer array containing the token value
         * from each {@code SplitPoint} in the same order they appear in the original list.
         *
         * @return A new {@code int[]} array of all token values. Never returns null.
         * @see SplitPoint#token()
         */
        public int[] getSplitTokenArray() {
            int[] tokens = new int[mSplitPoints.size()];
            int i = 0;
            for (SplitPoint sp : mSplitPoints) {
                tokens[i++] = sp.token();
            }
            return tokens;
        }

    }

    /**
@@ -384,30 +473,15 @@ public class AnrTimer<V> implements AutoCloseable {
    }

    /**
     * Generate a trace point with full timer information.  The meaning of milliseconds depends on
     * the caller.
     * Emit a trace instant event with an arbitrary list of arguments.
     * Arguments are comma-joined and wrapped in <code>op(...)</code>.
     */
    private void trace(String op, int timerId, int pid, int uid, long milliseconds) {
        final String label =
                formatSimple("%s(%d,%d,%d,%s,%d)", op, timerId, pid, uid, mLabel, milliseconds);
        Trace.instantForTrack(TRACE_TAG, TRACK, label);
        if (DEBUG) Log.i(TAG, label);
    }
    private static void trace(String op, Object... args) {
        StringJoiner joiner = new StringJoiner(",", op + "(", ")");

    /**
     * Generate a trace point with just the timer ID.
     */
    private void trace(String op, int timerId) {
        final String label = formatSimple("%s(%d)", op, timerId);
        Trace.instantForTrack(TRACE_TAG, TRACK, label);
        if (DEBUG) Log.i(TAG, label);
    }
        for (Object arg : args) joiner.add(String.valueOf(arg));

    /**
     * Generate a trace point with a pid and uid but no timer ID.
     */
    private static void trace(String op, int pid, int uid) {
        final String label = formatSimple("%s(%d,%d)", op, pid, uid);
        final String label = joiner.toString();
        Trace.instantForTrack(TRACE_TAG, TRACK, label);
        if (DEBUG) Log.i(TAG, label);
    }
@@ -514,7 +588,8 @@ public class AnrTimer<V> implements AutoCloseable {

        /** Create the native AnrTimerService that will host all timers from this instance. */
        FeatureEnabled() {
            mNative = nativeAnrTimerCreate(mLabel, mArgs.mExtend);
            mNative = nativeAnrTimerCreate(mLabel, mArgs.mExtend, mArgs.getSplitPercentArray(),
                    mArgs.getSplitTokenArray());
            if (mNative == 0) throw new IllegalArgumentException("unable to create native timer");
            synchronized (sAnrTimerList) {
                sAnrTimerList.put(mNative, new WeakReference(AnrTimer.this));
@@ -753,7 +828,7 @@ public class AnrTimer<V> implements AutoCloseable {
     */
    @Keep
    private boolean expire(int timerId, int pid, int uid, long elapsedMs) {
        trace("expired", timerId, pid, uid, elapsedMs);
        trace("expired", timerId, pid, uid, mLabel, elapsedMs);
        V arg = null;
        synchronized (mLock) {
            arg = mTimerArgMap.get(timerId);
@@ -769,6 +844,30 @@ public class AnrTimer<V> implements AutoCloseable {
        return true;
    }

    /**
     * Called when a timer reaches an early notification split point.
     * This allows for proactive actions before the timer fully expires.
     *
     * @param timerId the timer ID
     * @param pid pid of the timed operation
     * @param uid uid of the timed operation
     * @param elapsedMs milliseconds elapsed since timer start
     * @param token identifies the type of early notification
     */
    @Keep
    private void notifyEarly(int timerId, int pid, int uid,
                            long elapsedMs, int token) {
        trace("notifyEarly", timerId, pid, uid, mLabel, elapsedMs, token);
        switch(token) {
            case TOKEN_LONG_METHOD_TRACING:
                LongMethodTracer.trigger(pid,
                        (int) Math.max(MIN_LMT_DURATION_MS, elapsedMs * 1.5));
                break;
            default:
                Log.w(TAG, "Received a notification with an unknown token: " + token);
        }
    }

    /**
     * Close the object and free any native resources.
     */
@@ -931,7 +1030,8 @@ public class AnrTimer<V> implements AutoCloseable {
     * Unlike the other methods, this is an instance method: the "this" parameter is passed into
     * the native layer.
     */
    private native long nativeAnrTimerCreate(String name, boolean extend);
    private native long nativeAnrTimerCreate(String name, boolean extend,
            int[] splitPercent, int[] splitToken);

    /** Release the native resources.  No further operations are premitted. */
    private static native int nativeAnrTimerClose(long service);
+140 −72
Original line number Diff line number Diff line
@@ -34,21 +34,20 @@
#define ATRACE_TAG ATRACE_TAG_ACTIVITY_MANAGER
#define ANR_TIMER_TRACK "AnrTimerTrack"

#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <android_runtime/AndroidRuntime.h>
#include <core_jni_helpers.h>

#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <processgroup/processgroup.h>
#include <utils/Log.h>
#include <utils/Mutex.h>
#include <utils/Timers.h>
#include <utils/Trace.h>

#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>

using ::android::base::StringPrintf;


@@ -132,6 +131,32 @@ std::string getProcessName(pid_t pid) {
    }
}

/**
 * Actions that can be taken when a timer reaches a split point.
 * - Trace: Log the event for debugging
 * - Expire: Immediately expire the timer
 * - EarlyNotify: Send early notification to Java layer
 */
enum class SplitAction : uint8_t { Trace, Expire, EarlyNotify };

/**
 * Represents a point during timer execution where an action should be taken.
 * Split points are defined as percentages of the total timeout.
 */
struct SplitPoint {
    static constexpr uint32_t NOTOKEN = 0;
    // Percentage of timeout (1-99)
    uint8_t percent;
    // Action to take at this point
    SplitAction action;
    // Optional token for later identification
    uint32_t token = NOTOKEN;
    /* natural sort order, by percent */
    bool operator<(const SplitPoint& r) const {
        return percent < r.percent;
    }
};

/**
 * This class captures tracing information for processes tracked by an AnrTimer.  A user can
 * configure tracing to have the AnrTimerService emit extra information for watched processes.
@@ -457,8 +482,8 @@ class AnrTimerService {
    // A notifier is called with a timer ID, the timer's tag, and the client's cookie.  The pid
    // and uid that were originally assigned to the timer are passed as well.  The elapsed time
    // is the time since the timer was scheduled.
    using notifier_t = bool (*)(timer_id_t, int pid, int uid, nsecs_t elapsed,
                                void* cookie, jweak object);
    using notifier_t = bool (*)(timer_id_t, int pid, int uid, nsecs_t elapsed, void* cookie,
                                jweak object, bool expired, uint32_t token);

    enum Status {
        Invalid,
@@ -474,7 +499,7 @@ class AnrTimerService {
     * configuration options.
     */
    AnrTimerService(const char* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*,
                    bool extend);
                    bool extend, std::vector<SplitPoint> splits);

    // Delete the service and clean up memory.
    ~AnrTimerService();
@@ -576,6 +601,9 @@ class AnrTimerService {

    // The global tracing specification.
    static AnrTimerTracer tracer_;

    // Default split points for any timer in this service
    std::vector<SplitPoint> defaultSplits_;
};

AnrTimerTracer AnrTimerService::tracer_;
@@ -634,13 +662,10 @@ class AnrTimerService::Timer {
    const nsecs_t timeout;
    // True if the timer may be extended.
    const bool extend;
    // This is a percentage between 0 and 100.  If it is non-zero then timer will fire at
    // timeout*split/100, and the EarlyAction will be invoked.  The timer may continue running
    // or may expire, depending on the action.  Thus, this value "splits" the timeout into two
    // pieces.
    const int split;
    // The action to take if split (above) is non-zero, when the timer reaches the split point.
    const AnrTimerTracer::EarlyAction action;
    // The splits and actions to take before the timer expire
    std::vector<SplitPoint> splits;
    // index of the next split to fire
    uint8_t nextSplit;

    // The state of this timer.
    Status status;
@@ -651,12 +676,12 @@ class AnrTimerService::Timer {
    // The scheduled timeout.  This is an absolute time.  It may be extended.
    nsecs_t scheduled;

    // True if this timer is split and in its second half
    bool splitting;

    // True if this timer has been extended.
    bool extended;

    // True if tracing is enabled for this timer.
    bool traced;

    // Bookkeeping for extensions.  The initial state of the process.  This is collected only if
    // the timer is extensible.
    ProcessStats initial;
@@ -667,36 +692,35 @@ class AnrTimerService::Timer {

    // This constructor creates a timer with the specified id and everything else set to
    // "empty".  This can be used as the argument to find().
    Timer(timer_id_t id) :
            id(id),
    Timer(timer_id_t id)
          : id(id),
            pid(0),
            uid(0),
            timeout(0),
            extend(false),
            split(0),
            action(AnrTimerTracer::None),
            nextSplit(0),
            status(Invalid),
            started(0),
            scheduled(0),
            splitting(false),
            extended(false) {
    }
            extended(false),
            traced(false) {}

    // Create a new timer.  This starts the timer.
    Timer(int pid, int uid, nsecs_t timeout, bool extend, AnrTimerTracer::TraceConfig trace) :
            id(nextId()),
    Timer(int pid, int uid, nsecs_t timeout, bool extend, AnrTimerTracer::TraceConfig trace,
          std::vector<SplitPoint> splits)
          : id(nextId()),
            pid(pid),
            uid(uid),
            timeout(timeout),
            extend(extend),
            split(trace.earlyTimeout),
            action(trace.action),
            splits(buildSplits(std::move(splits), trace)),
            nextSplit(0),
            status(Running),
            started(now()),
            scheduled(started + (split > 0 ? (timeout*split)/100 : timeout)),
            splitting(false),
            extended(false) {

            scheduled(started +
                      (splits.size() > 0 ? (timeout * splits[0].percent) / 100 : timeout)),
            extended(false),
            traced(trace.enabled) {
        if (extend && pid != 0) {
            initial.fill(pid);
        }
@@ -717,21 +741,26 @@ class AnrTimerService::Timer {
    // Expire a timer. Return true if the timer is expired and false otherwise.  The function
    // returns false if the timer is eligible for extension.  If the function returns false, the
    // scheduled time is updated.
    bool expire() {
        if (split > 0 && !splitting) {
            scheduled = started + timeout;
            splitting = true;
    std::pair<bool, uint32_t> expire() {
        if (nextSplit < splits.size()) {
            const SplitPoint& point = splits[nextSplit++];
            scheduled = (nextSplit < splits.size())
                    ? started + timeout * splits[nextSplit].percent / 100
                    : started + timeout;
            switch (point.action) {
                case SplitAction::Trace:
                    event("split");
            switch (action) {
                case AnrTimerTracer::None:
                case AnrTimerTracer::Trace:
                    break;
                case AnrTimerTracer::Expire:
                case SplitAction::EarlyNotify:
                    // notify the timer
                    return {true, point.token};
                    break;
                case SplitAction::Expire:
                    status = Expired;
                    event("expire");
                    break;
            }
            return status == Expired;
            return {status == Expired, SplitPoint::NOTOKEN};
        }

        nsecs_t extension = 0;
@@ -751,7 +780,7 @@ class AnrTimerService::Timer {
            scheduled += extension;
            event("extend");
        }
        return status == Expired;
        return {status == Expired, SplitPoint::NOTOKEN};
    }

    // Accept a timeout.  This does nothing other than log the state machine change.
@@ -818,7 +847,7 @@ class AnrTimerService::Timer {

    // Log an event, guarded by the debug flag.
    void event(const char* tag, bool verbose) {
        if (action != AnrTimerTracer::None) {
        if (traced) {
            char msg[PATH_MAX];
            snprintf(msg, sizeof(msg), "%s(pid=%d)", tag, pid);
            traceEvent(msg);
@@ -836,6 +865,16 @@ class AnrTimerService::Timer {
        ATRACE_INSTANT_FOR_TRACK(ANR_TIMER_TRACK, msg);
    }

    static std::vector<SplitPoint> buildSplits(std::vector<SplitPoint> splits,
                                               const AnrTimerTracer::TraceConfig& cfg) {
        if (cfg.earlyTimeout > 0) {
            SplitAction action = (cfg.action == AnrTimerTracer::Expire) ? SplitAction::Expire
                                                                        : SplitAction::Trace;
            splits.emplace_back(static_cast<uint8_t>(cfg.earlyTimeout), action);
        }
        std::sort(splits.begin(), splits.end());
        return splits;
    }
    // IDs start at 1.  A zero ID is invalid.
    static std::atomic<timer_id_t> idGen;
};
@@ -1063,16 +1102,15 @@ class AnrTimerService::Ticker {

std::atomic<size_t> AnrTimerService::Ticker::idGen_;


AnrTimerService::AnrTimerService(const char* label, notifier_t notifier, void* cookie,
            jweak jtimer, Ticker* ticker, bool extend) :
        label_(label),
AnrTimerService::AnrTimerService(const char* label, notifier_t notifier, void* cookie, jweak jtimer,
                                 Ticker* ticker, bool extend, std::vector<SplitPoint> splits)
      : label_(label),
        notifier_(notifier),
        notifierCookie_(cookie),
        notifierObject_(jtimer),
        extend_(extend),
        ticker_(ticker) {

        ticker_(ticker),
        defaultSplits_(std::move(splits)) {
    // Zero the statistics
    maxRunning_ = 0;
    memset(&counters_, 0, sizeof(counters_));
@@ -1097,7 +1135,7 @@ const char* AnrTimerService::statusString(Status s) {

AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) {
    AutoMutex _l(lock_);
    Timer t(pid, uid, timeout, extend_, tracer_.getConfig(pid));
    Timer t(pid, uid, timeout, extend_, tracer_.getConfig(pid), defaultSplits_);
    insertLocked(t);
    t.start();
    counters_.started++;
@@ -1156,11 +1194,14 @@ void AnrTimerService::expire(timer_id_t timerId) {
    int pid = 0;
    int uid = 0;
    nsecs_t elapsed = 0;
    bool notify = false;
    bool expired = false;
    uint32_t token = SplitPoint::NOTOKEN;
    {
        AutoMutex _l(lock_);
        Timer t = removeLocked(timerId);
        expired = t.expire();
        std::tie(notify, token) = t.expire();
        expired = t.status == Expired;
        if (t.status == Invalid) {
            ALOGW_IF(DEBUG_ERROR, "error: expired invalid timer %u", timerId);
            return;
@@ -1181,8 +1222,9 @@ void AnrTimerService::expire(timer_id_t timerId) {
    }

    // Deliver the notification outside of the lock.
    if (expired) {
        if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
    if (notify) {
        if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_, expired,
                       token)) {
            // Notification failed, which means the listener will never call accept() or
            // discard().  Do not reinsert the timer.
            discard(timerId);
@@ -1250,6 +1292,7 @@ static Mutex gAnrLock;
struct AnrArgs {
    jclass clazz = NULL;
    jmethodID func = NULL;
    jmethodID funcEarly = NULL;
    JavaVM* vm = NULL;
    AnrTimerService::Ticker* ticker = nullptr;
};
@@ -1257,7 +1300,7 @@ static AnrArgs gAnrArgs;

// The cookie is the address of the AnrArgs object to which the notification should be sent.
static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid, nsecs_t elapsed,
                      void* cookie, jweak jtimer) {
                      void* cookie, jweak jtimer, bool expired, uint32_t token) {
    AutoMutex _l(gAnrLock);
    AnrArgs* target = reinterpret_cast<AnrArgs* >(cookie);
    JNIEnv *env;
@@ -1268,8 +1311,13 @@ static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid, nse
    jboolean r = false;
    jobject timer = env->NewGlobalRef(jtimer);
    if (timer != nullptr) {
        // Convert the elsapsed time from ns (native) to ms (Java)
        if (expired) {
            r = env->CallBooleanMethod(timer, target->func, timerId, pid, uid, ns2ms(elapsed));
        } else {
            env->CallVoidMethod(timer, target->funcEarly, timerId, pid, uid, ns2ms(elapsed), token);
            r = true;
        }

        env->DeleteGlobalRef(timer);
    }
    target->vm->DetachCurrentThread();
@@ -1280,17 +1328,35 @@ jboolean anrTimerSupported(JNIEnv* env, jclass) {
    return nativeSupportEnabled;
}

jlong anrTimerCreate(JNIEnv* env, jobject jtimer, jstring jname, jboolean extend) {
jlong anrTimerCreate(JNIEnv* env, jobject jtimer, jstring jname, jboolean extend, jintArray jperc,
                     jintArray jtok) {
    if (!nativeSupportEnabled) return 0;
    AutoMutex _l(gAnrLock);
    if (gAnrArgs.ticker == nullptr) {
        gAnrArgs.ticker = new AnrTimerService::Ticker();
    }

    std::vector<SplitPoint> splits;
    if (jperc && jtok) {
        const ScopedIntArrayRO percents(env, jperc);
        const ScopedIntArrayRO tokens(env, jtok);

        // There is a size mismatch, return an error
        if (percents.size() != tokens.size()) return 0;

        const jsize n = percents.size();
        splits.reserve(n);

        for (jsize i = 0; i < n; ++i) {
            splits.emplace_back(percents[i], SplitAction::EarlyNotify, tokens[i]);
        }
        std::sort(splits.begin(), splits.end());
    }

    ScopedUtfChars name(env, jname);
    jobject timer = env->NewWeakGlobalRef(jtimer);
    AnrTimerService* service = new AnrTimerService(name.c_str(),
        anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend);
    AnrTimerService* service = new AnrTimerService(name.c_str(), anrNotify, &gAnrArgs, timer,
                                                   gAnrArgs.ticker, extend, std::move(splits));
    return reinterpret_cast<jlong>(service);
}

@@ -1356,7 +1422,7 @@ jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) {

static const JNINativeMethod methods[] = {
        {"nativeAnrTimerSupported", "()Z", (void*)anrTimerSupported},
    {"nativeAnrTimerCreate",      "(Ljava/lang/String;Z)J", (void*) anrTimerCreate},
        {"nativeAnrTimerCreate", "(Ljava/lang/String;Z[I[I)J", (void*)anrTimerCreate},
        {"nativeAnrTimerClose", "(J)I", (void*)anrTimerClose},
        {"nativeAnrTimerStart", "(JIIJ)I", (void*)anrTimerStart},
        {"nativeAnrTimerCancel", "(JI)Z", (void*)anrTimerCancel},
@@ -1381,6 +1447,8 @@ int register_android_server_utils_AnrTimer(JNIEnv* env)
    jclass service = FindClassOrDie(env, className);
    gAnrArgs.clazz = MakeGlobalRefOrDie(env, service);
    gAnrArgs.func = env->GetMethodID(gAnrArgs.clazz, "expire", "(IIIJ)Z");
    gAnrArgs.funcEarly = env->GetMethodID(gAnrArgs.clazz, "notifyEarly", "(IIIJI)V");

    env->GetJavaVM(&gAnrArgs.vm);

    return 0;