ConcurrencyHelpers.java

/*
 * Copyright 2021 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.emoji2.text;

import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;

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

import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Various (internal) helpers for interacting with threads and event loops
 */
class ConcurrencyHelpers {

    // It is expected that all callers of this internal API call shutdown on completion, allow 15
    // seconds for retry delay before spinning down the thread.
    private static final int FONT_LOAD_TIMEOUT_SECONDS = 15 /* seconds */;

    private ConcurrencyHelpers() { /* can't instantiate */ }

    /**
     * Background thread worker with an explicit thread name.
     *
     * It is expected that callers explicitly shut down the returned ThreadPoolExecutor as soon
     * as they have completed font loading.
     *
     * @param name name of thread
     * @return ThreadPoolExecutor limited to one thread with a timeout of 15 seconds.
     */
    @SuppressWarnings("ThreadPriorityCheck")
    static ThreadPoolExecutor createBackgroundPriorityExecutor(@NonNull String name) {
        ThreadFactory threadFactory = runnable -> {
            Thread t = new Thread(runnable, name);
            t.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return t;
        };
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                0 /* corePoolSize */,
                1 /* maximumPoolSize */,
                FONT_LOAD_TIMEOUT_SECONDS /* keepAliveTime */,
                TimeUnit.SECONDS /* keepAliveTime TimeUnit */,
                new LinkedBlockingDeque<>() /* unbounded queue*/,
                threadFactory
        );
        executor.allowCoreThreadTimeOut(true);
        return executor;
    }

    /**
     * @return Main thread handler, with createAsync if API level supports it.
     */
    static Handler mainHandlerAsync() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return Handler28Impl.createAsync(Looper.getMainLooper());
        } else {
            return new Handler(Looper.getMainLooper());
        }
    }

    /**
     * @deprecated Exists only for upgrade path, remove with
     * {@link FontRequestEmojiCompatConfig#setHandler(Handler)}
     *
     * @param handler a background thread handler
     * @return an executor that posts all work to that handler
     */
    @NonNull
    @Deprecated
    static Executor convertHandlerToExecutor(@NonNull Handler handler) {
        return handler::post;
    }

    @RequiresApi(28)
    static class Handler28Impl {
        private Handler28Impl() {
            // Non-instantiable.
        }

        @DoNotInline
        public static Handler createAsync(Looper looper) {
            return Handler.createAsync(looper);
        }
    }
}