/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* src/com/vodafone360/people/VODAFONE.LICENSE.txt or
* http://github.com/360/360-Engine-for-Android
* See the License for the specific language governing permissions and
* limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2010 Vodafone Sales & Services Ltd. All rights reserved.
* Use is subject to license terms.
*/
package com.vodafone360.people.service;
import android.os.Handler;
import com.vodafone360.people.Settings;
import com.vodafone360.people.engine.EngineManager;
import com.vodafone360.people.utils.LogUtils;
/**
* The worker thread is the main thread of execution within the People Client
* service. It is responsible for running engines via the EngineManager The
* WorkerThread remains active while there are engines with pending run times,
* becoming dormant otherwise The WorkerThread can be woken up when a request is
* issued or via an Alarm set to trigger an engine runtime update check It is
* extremely important that the WorkerThread does not become blocked.
*/
public class WorkerThread extends Thread {
/**
* Name of alarm for waking up worker thread
*/
public final static String ALARM_WORKER_THREAD = "worker_thread";
/**
* Worker thread is renamed to this string
*/
private final static String WORKER_THREAD_NAME = "WorkerThread";
/**
* Small wait time before looping around the engines again
*/
private final static long LOOP_SLEEP_TIME_MS = 300;
/**
* Maximum loop iterations before allowing the worker thread to sleep for a
* while to preserve battery.
*
* @note Maybe this should be >1000 to avoid breaking startup sync TODO:
* Make this stricter as the code base improves.
*/
private final static int MAX_RUNS_WITHOUT_PAUSE = 1000;
/**
* Period of time to put worker thread to sleep after it has run
* {@link #MAX_RUNS_WITHOUT_PAUSE} times without any pause. TODO: Make this
* stricter as the code base improves.
*/
private final static int FORCE_PAUSE_MS = 10 * 1000; // Ten seconds
/**
* Object is used to control the worker thread. When no work is needed the
* thread will wait on this object.
*/
private final Object mWakeLock = new Object();
/**
* Reference to the {@link EngineManager}.
*/
private EngineManager mEngineManager;
/**
* {@link Handler} used so that threads can wake up the worker thread by
* sending a message.
*/
private Handler mHandler;
/**
* Set to true when any of the engines run at least once.
*/
private Boolean mLoopOneMoreTime = false;
/**
* Set to true when the worker thread is about to stop running.
*/
private boolean mShutdownThread = false;
/**
* @param context Context Android Application Context.
*/
protected WorkerThread(Handler handler) {
setName(WORKER_THREAD_NAME);
mHandler = handler;
mEngineManager = EngineManager.getInstance();
if (mEngineManager == null) {
throw new RuntimeException("WorkerThread - The EngineManager cannot be null");
}
}
/**
* Wake the worker thread.
*
* @return TRUE if thread is awakened successfully.
*/
protected boolean wakeUp() {
LogUtils.logD("WorkerThread.wakeUp()");
synchronized (mWakeLock) {
if (mShutdownThread) {
return false;
}
mLoopOneMoreTime = true;
mWakeLock.notifyAll();
return true;
}
}
/***
* Loop through all the engines until no more work can be done right now,
* then set an Alarm if more work is required.
*/
@Override
public void run() {
LogUtils.logD("WorkerThread.run() [Start Thread]");
int numberOfRunsWithoutPause = 0;
synchronized (mWakeLock) {
mShutdownThread = false;
}
do {
numberOfRunsWithoutPause++;
boolean prepareForShutdown = false;
long nextRunTime = mEngineManager.runEngines();
long currentTime = System.currentTimeMillis();
if (Settings.ENABLED_ENGINE_TRACE) {
LogUtils.logV("WorkerThread.run() mNextRunTime is [" + nextRunTime + "] or in "
+ "[" + (nextRunTime - currentTime) + "ms] - mNumberOfRunsWithoutPause "
+ "[" + numberOfRunsWithoutPause + "]");
}
if (nextRunTime == -1) {
if (Settings.ENABLED_ENGINE_TRACE) {
LogUtils.logV("WorkerThread.run() No Engines waiting, so stop thread without "
+ "creating a new Alarm");
}
prepareForShutdown = true;
} else if (nextRunTime == 0) {
if (Settings.ENABLED_ENGINE_TRACE) {
LogUtils.logV("WorkerThread.run() Engine waiting to run, so loop thread again");
}
} else if (nextRunTime < currentTime) {
if (Settings.ENABLED_ENGINE_TRACE) {
LogUtils.logV("WorkerThread.run() Next process is already ["
+ (currentTime - nextRunTime) + "ms] late, so loop again");
}
} else {
if (Settings.ENABLED_ENGINE_TRACE) {
LogUtils.logV("WorkerThread.run() No Engines ready yet, so set Alarm and then"
+ " end thread");
}
prepareForShutdown = true;
}
if (numberOfRunsWithoutPause > MAX_RUNS_WITHOUT_PAUSE) {
LogUtils.logE("WorkerThread.run() WorkerThread is looping like crazy, pausing for"
+ " [" + FORCE_PAUSE_MS + "ms] to save device resources.");
prepareForShutdown = true;
mLoopOneMoreTime = false;
nextRunTime = System.currentTimeMillis() + FORCE_PAUSE_MS;
}
LogUtils.logV("WorkerThread.run() prepareForShutdown[" + prepareForShutdown + "]"
+ " mLoopOneMoreTime[" + mLoopOneMoreTime + "]");
if (prepareForShutdown && !mLoopOneMoreTime) {
setWakeupTime(nextRunTime);
mShutdownThread = true;
mLoopOneMoreTime = false;
} else {
mLoopOneMoreTime = false;
threadLoopSleep();
}
} while (!mShutdownThread);
LogUtils.logD("WorkerThread.run() [End Thread]");
}
/***
* Pause the thread between cycles to stop it overloading the device and
* possibly blocking the UI.
*/
private void threadLoopSleep() {
synchronized (mWakeLock) {
try {
mWakeLock.wait(LOOP_SLEEP_TIME_MS);
} catch (InterruptedException e) {
// Do nothing
}
}
}
/***
* Wake up the WorkerThread at the given time.
*
* @param set TRUE sets the restart time, FALSE cancel any pending restarts.
*/
private void setWakeupTime(long nextRunTime) {
if (nextRunTime > System.currentTimeMillis()) {
LogUtils.logV("WorkerThread.setWakeupTime() Run again in ["
+ (nextRunTime - System.currentTimeMillis()) + "ms]");
mHandler.sendEmptyMessageDelayed(1, nextRunTime - System.currentTimeMillis());
} else if (nextRunTime != -1) {
LogUtils.logV("WorkerThread.setWakeupTime() Run again now, nextRunTime[" + nextRunTime
+ "]");
mHandler.sendEmptyMessage(1);
}
}
/**
* Shut down and join the running thread.
*/
protected void close() {
LogUtils.logV("WorkerThread.close()");
mShutdownThread = true;
try {
join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}