// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.base; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.MainDex; import java.lang.Thread.UncaughtExceptionHandler; /** * Thread in Java with an Android Handler. This class is not thread safe. */ @JNINamespace("base::android") @MainDex public class JavaHandlerThread { private final HandlerThread mThread; private Throwable mUnhandledException; /** * Construct a java-only instance. Can be connected with native side later. * Useful for cases where a java thread is needed before native library is loaded. */ public JavaHandlerThread(String name, int priority) { mThread = new HandlerThread(name, priority); } @CalledByNative private static JavaHandlerThread create(String name, int priority) { return new JavaHandlerThread(name, priority); } public Looper getLooper() { assert hasStarted(); return mThread.getLooper(); } public void maybeStart() { if (hasStarted()) return; mThread.start(); } @CalledByNative private void startAndInitialize(final long nativeThread, final long nativeEvent) { maybeStart(); new Handler(mThread.getLooper()).post(new Runnable() { @Override public void run() { nativeInitializeThread(nativeThread, nativeEvent); } }); } @CalledByNative private void quitThreadSafely(final long nativeThread) { // Allow pending java tasks to run, but don't run any delayed or newly queued up tasks. new Handler(mThread.getLooper()).post(new Runnable() { @Override public void run() { mThread.quit(); nativeOnLooperStopped(nativeThread); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { // When we can, signal that new tasks queued up won't be run. mThread.getLooper().quitSafely(); } } @CalledByNative private void joinThread() { boolean joined = false; while (!joined) { try { mThread.join(); joined = true; } catch (InterruptedException e) { } } } private boolean hasStarted() { return mThread.getState() != Thread.State.NEW; } @CalledByNative private boolean isAlive() { return mThread.isAlive(); } // This should *only* be used for tests. In production we always need to call the original // uncaught exception handler (the framework's) after any uncaught exception handling we do, as // it generates crash dumps and kills the process. @CalledByNative private void listenForUncaughtExceptionsForTesting() { mThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { mUnhandledException = e; } }); } @CalledByNative private Throwable getUncaughtExceptionIfAny() { return mUnhandledException; } private native void nativeInitializeThread(long nativeJavaHandlerThread, long nativeEvent); private native void nativeOnLooperStopped(long nativeJavaHandlerThread); }