blob: 162838779c42c35e9e02d39385b2c677b2aff631 [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.providers.contacts;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.GuardedBy;
/**
* Runs tasks in a worker thread, which is created on-demand and shuts down after a timeout.
*/
public abstract class ContactsTaskScheduler {
private static final String TAG = "ContactsTaskScheduler";
public static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
private static final int SHUTDOWN_TIMEOUT_SECONDS = 60;
private final AtomicInteger mThreadSequenceNumber = new AtomicInteger();
private final Object mLock = new Object();
/**
* Name of this scheduler for logging.
*/
private final String mName;
@GuardedBy("mLock")
private HandlerThread mThread;
@GuardedBy("mLock")
private MyHandler mHandler;
private final int mShutdownTimeoutSeconds;
public ContactsTaskScheduler(String name) {
this(name, SHUTDOWN_TIMEOUT_SECONDS);
}
/** With explicit timeout seconds, for testing. */
protected ContactsTaskScheduler(String name, int shutdownTimeoutSeconds) {
mName = name;
mShutdownTimeoutSeconds = shutdownTimeoutSeconds;
}
private class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "[" + mName + "] " + mThread + " dispatching " + msg.what);
}
onPerformTask(msg.what, msg.obj);
}
}
private final Runnable mQuitter = () -> {
synchronized (mLock) {
stopThread(/* joinOnlyForTest=*/ false);
}
};
private boolean isRunning() {
synchronized (mLock) {
return mThread != null;
}
}
/** Schedule a task with no arguments. */
@VisibleForTesting
public void scheduleTask(int taskId) {
scheduleTask(taskId, null);
}
/** Schedule a task with an argument. */
@VisibleForTesting
public void scheduleTask(int taskId, Object arg) {
synchronized (mLock) {
if (!isRunning()) {
mThread = new HandlerThread("Worker-" + mThreadSequenceNumber.incrementAndGet());
mThread.start();
mHandler = new MyHandler(mThread.getLooper());
if (VERBOSE_LOGGING) {
Log.v(TAG, "[" + mName + "] " + mThread + " started.");
}
}
if (arg == null) {
mHandler.sendEmptyMessage(taskId);
} else {
mHandler.sendMessage(mHandler.obtainMessage(taskId, arg));
}
// Schedule thread shutdown.
mHandler.removeCallbacks(mQuitter);
mHandler.postDelayed(mQuitter, mShutdownTimeoutSeconds * 1000);
}
}
public abstract void onPerformTask(int taskId, Object arg);
@VisibleForTesting
public void shutdownForTest() {
stopThread(/* joinOnlyForTest=*/ true);
}
private void stopThread(boolean joinOnlyForTest) {
synchronized (mLock) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "[" + mName + "] " + mThread + " stopping...");
}
if (mThread != null) {
mThread.quit();
if (joinOnlyForTest) {
try {
mThread.join();
} catch (InterruptedException ignore) {
}
}
}
mThread = null;
mHandler = null;
}
}
@VisibleForTesting
public int getThreadSequenceNumber() {
return mThreadSequenceNumber.get();
}
@VisibleForTesting
public boolean isRunningForTest() {
return isRunning();
}
}