Threads.java

/*
 * Copyright 2019 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 androidx.camera.core.impl.utils;

import static androidx.core.util.Preconditions.checkState;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Helpers for {@link Thread}s.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class Threads {
    private static final long TIMEOUT_RUN_ON_MAIN_MS = 30_000L; // milliseconds

    // Prevent instantiation.
    private Threads() {
    }

    /** Returns true if we're currently running in the application's main thread. */
    public static boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

    /** Returns true if we're currently running on a background thread. */
    public static boolean isBackgroundThread() {
        return !isMainThread();
    }

    /**
     * Ensures that we're currently running in the application's main thread.
     *
     * @throws IllegalStateException If the caller is not running on the main thread,
     */
    public static void checkMainThread() {
        checkState(isMainThread(), "Not in application's main thread");
    }

    /**
     * Ensures that we're currently not running in the application's main thread.
     *
     * @throws IllegalStateException if the caller is running on the main thread.
     */
    public static void checkBackgroundThread() {
        checkState(isBackgroundThread(), "In application's main thread");
    }
    /**
     * Executes the {@link Runnable} on main thread.
     *
     * <p>If the caller thread is already main thread, then runnable will be executed immediately.
     * Otherwise, the runnable will be posted to main thread.
     */
    public static void runOnMain(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
            return;
        }
        checkState(getMainHandler().post(runnable), "Unable to post to main thread");
    }

    /**
     * Executes the {@link Runnable} on main thread and block until the Runnable is complete.
     *
     * <p>If the caller thread is already main thread, then runnable will be executed immediately.
     * Otherwise, the runnable will be posted to main thread and caller thread will be blocked until
     * the runnable is complete.
     *
     * <p> A 30 second timeout is basically to prevent unit tests from waiting infinitely if
     * there is any error. Normal flow should not expect this timeout. Basically main
     * thread should not be occupied for too long or an ANR could occur.
     *
     * @param runnable the runnable to execute.
     *
     * @throws IllegalStateException if timed out waiting for the posted runnable to complete.
     * @throws InterruptedRuntimeException if the waiting is interrupted.
     */
    public static void runOnMainSync(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
            return;
        }
        // Post to main thread and wait for the completion.
        CountDownLatch latch = new CountDownLatch(1);
        boolean postResult = getMainHandler().post(() -> {
            try {
                runnable.run();
            } finally {
                latch.countDown();
            }
        });
        checkState(postResult, "Unable to post to main thread");
        try {
            if (!latch.await(TIMEOUT_RUN_ON_MAIN_MS, TimeUnit.MILLISECONDS)) {
                throw new IllegalStateException("Timeout to wait main thread execution");
            }
        } catch (InterruptedException e) {
            throw new InterruptedRuntimeException(e);
        }
    }

    @NonNull
    private static Handler getMainHandler() {
        return new Handler(Looper.getMainLooper());
    }
}