package com.basic.security.utils;/* * The MIT License (MIT) * * Copyright (c) 2016 Salomon BRYS * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import android.os.Debug; import android.os.Handler; import android.os.Looper; import android.util.Log; /** * A watchdog timer thread that detects when the UI thread has frozen. */ @SuppressWarnings("UnusedReturnValue") public class ANRWatchDog extends Thread { private static final int DEFAULT_ANR_TIMEOUT = 10 * 1000; private static final ANRListener DEFAULT_ANR_LISTENER = new ANRListener() { @Override public void onAppNotResponding(ANRError error) { throw error; } }; private static final ANRInterceptor DEFAULT_ANR_INTERCEPTOR = new ANRInterceptor() { @Override public long intercept(long duration) { return 0; } }; private static final InterruptionListener DEFAULT_INTERRUPTION_LISTENER = new InterruptionListener() { @Override public void onInterrupted(InterruptedException exception) { Log.w("ANRWatchdog", "Interrupted: " + exception.getMessage()); } }; private final Handler _uiHandler = new Handler(Looper.getMainLooper()); private final int _timeoutInterval; private ANRListener _anrListener = DEFAULT_ANR_LISTENER; private ANRInterceptor _anrInterceptor = DEFAULT_ANR_INTERCEPTOR; private InterruptionListener _interruptionListener = DEFAULT_INTERRUPTION_LISTENER; private String _namePrefix = ""; private boolean _logThreadsWithoutStackTrace = false; private boolean _ignoreDebugger = false; private volatile long _tick = 0; private volatile boolean _reported = false; private final Runnable _ticker = new Runnable() { @Override public void run() { _tick = 0; _reported = false; } }; /** * Constructs a watchdog that checks the ui thread every {@value #DEFAULT_ANR_TIMEOUT} milliseconds */ public ANRWatchDog() { this(DEFAULT_ANR_TIMEOUT); } /** * Constructs a watchdog that checks the ui thread every given interval * * @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread. * It is therefore the maximum time the UI may freeze before being reported as ANR. */ public ANRWatchDog(int timeoutInterval) { super(); _timeoutInterval = timeoutInterval; } /** * @return The interval the WatchDog */ public int getTimeoutInterval() { return _timeoutInterval; } /** * Sets an interface for when an ANR is detected. * If not set, the default behavior is to throw an error and crash the application. * * @param listener The new listener or null * @return itself for chaining. */ public ANRWatchDog setANRListener(ANRListener listener) { if (listener == null) { _anrListener = DEFAULT_ANR_LISTENER; } else { _anrListener = listener; } return this; } /** * Sets an interface to intercept ANRs before they are reported. * If set, you can define if, given the current duration of the detected ANR and external context, it is necessary to report the ANR. * * @param interceptor The new interceptor or null * @return itself for chaining. */ public ANRWatchDog setANRInterceptor(ANRInterceptor interceptor) { if (interceptor == null) { _anrInterceptor = DEFAULT_ANR_INTERCEPTOR; } else { _anrInterceptor = interceptor; } return this; } /** * Sets an interface for when the watchdog thread is interrupted. * If not set, the default behavior is to just log the interruption message. * * @param listener The new listener or null. * @return itself for chaining. */ public ANRWatchDog setInterruptionListener(InterruptionListener listener) { if (listener == null) { _interruptionListener = DEFAULT_INTERRUPTION_LISTENER; } else { _interruptionListener = listener; } return this; } /** * Set the prefix that a thread's name must have for the thread to be reported. * Note that the main thread is always reported. * Default "". * * @param prefix The thread name's prefix for a thread to be reported. * @return itself for chaining. */ public ANRWatchDog setReportThreadNamePrefix(String prefix) { if (prefix == null) { prefix = ""; } _namePrefix = prefix; return this; } /** * Set that only the main thread will be reported. * * @return itself for chaining. */ public ANRWatchDog setReportMainThreadOnly() { _namePrefix = null; return this; } /** * Set that all threads will be reported (default behaviour). * * @return itself for chaining. */ public ANRWatchDog setReportAllThreads() { _namePrefix = ""; return this; } /** * Set that all running threads will be reported, * even those from which no stack trace could be extracted. * Default false. * * @param logThreadsWithoutStackTrace Whether or not all running threads should be reported * @return itself for chaining. */ public ANRWatchDog setLogThreadsWithoutStackTrace(boolean logThreadsWithoutStackTrace) { _logThreadsWithoutStackTrace = logThreadsWithoutStackTrace; return this; } /** * Set whether to ignore the debugger when detecting ANRs. * When ignoring the debugger, ANRWatchdog will detect ANRs even if the debugger is connected. * By default, it does not, to avoid interpreting debugging pauses as ANRs. * Default false. * * @param ignoreDebugger Whether to ignore the debugger. * @return itself for chaining. */ public ANRWatchDog setIgnoreDebugger(boolean ignoreDebugger) { _ignoreDebugger = ignoreDebugger; return this; } @SuppressWarnings("NonAtomicOperationOnVolatileField") @Override public void run() { setName("|ANR-WatchDog|"); long interval = _timeoutInterval; while (!isInterrupted()) { boolean needPost = _tick == 0; _tick += interval; if (needPost) { _uiHandler.post(_ticker); } try { Thread.sleep(interval); } catch (InterruptedException e) { _interruptionListener.onInterrupted(e); return; } // If the main thread has not handled _ticker, it is blocked. ANR. if (_tick != 0 && !_reported) { //noinspection ConstantConditions if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) { Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))"); _reported = true; continue; } interval = _anrInterceptor.intercept(_tick); if (interval > 0) { continue; } final ANRError error; if (_namePrefix != null) { System.out.println("ANRWatchDog.run 1"); error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace); } else { System.out.println("ANRWatchDog.run 2"); error = ANRError.NewMainOnly(_tick); } _anrListener.onAppNotResponding(error); interval = _timeoutInterval; _reported = true; } } } public interface ANRListener { /** * Called when an ANR is detected. * * @param error The error describing the ANR. */ void onAppNotResponding(ANRError error); } public interface ANRInterceptor { /** * Called when main thread has froze more time than defined by the timeout. * * @param duration The minimum time (in ms) the main thread has been frozen (may be more). * @return 0 or negative if the ANR should be reported immediately. A positive number of ms to postpone the reporting. */ long intercept(long duration); } public interface InterruptionListener { void onInterrupted(InterruptedException exception); } }