package org.commcare.tasks.templates;
import android.os.AsyncTask;
import android.util.Log;
import org.commcare.logging.UserCausedRuntimeException;
import org.commcare.utils.ACRAUtil;
import org.javarosa.core.services.Logger;
/**
* @author ctsims
*/
public abstract class CommCareTask<Params, Progress, Result, Receiver>
extends ManagedAsyncTask<Params, Progress, Result> {
protected static String TAG;
public static final int GENERIC_TASK_ID = 32;
public static final int DONT_WAKELOCK = -1;
private final Object connectorLock = new Object();
private CommCareTaskConnector<Receiver> connector;
private Exception unknownError;
protected int taskId = GENERIC_TASK_ID;
//Wait for 2 seconds for something to reconnnect for now (very high)
private static final int ALLOWABLE_CONNECTOR_ACQUISITION_DELAY = 2000;
protected CommCareTask() {
TAG = CommCareTask.class.getSimpleName();
}
@Override
protected final Result doInBackground(Params... params) {
//Never have to wrap the entirety of your task.
try {
return doTaskBackground(params);
} catch (Exception e) {
Logger.log(TAG, e.getMessage());
e.printStackTrace();
if (!(e instanceof UserCausedRuntimeException)) {
// Report crashes we know weren't caused by user misconfiguration
ACRAUtil.reportException(e);
}
// Save error for reporting during post-execute
unknownError = e;
return null;
}
}
/**
* Catch-wrapped computation to be performed in background thread.
* Dispatched by doInBackground
*/
protected abstract Result doTaskBackground(Params... params);
@Override
protected void onCancelled() {
super.onCancelled();
synchronized (connectorLock) {
CommCareTaskConnector<Receiver> connector = getConnector();
if (connector != null) {
connector.startTaskTransition();
connector.stopBlockingForTask(getTaskId());
connector.taskCancelled();
connector.stopTaskTransition();
}
}
}
@Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
synchronized (connectorLock) {
//TODO: extend blocking here?
CommCareTaskConnector<Receiver> connector = getConnector();
if (connector != null) {
connector.startTaskTransition();
connector.stopBlockingForTask(taskId);
if (unknownError != null) {
deliverError(connector.getReceiver(), unknownError);
} else {
deliverResult(connector.getReceiver(), result);
}
connector.stopTaskTransition();
}
}
}
protected abstract void deliverResult(Receiver receiver, Result result);
protected abstract void deliverUpdate(Receiver receiver, Progress... update);
protected abstract void deliverError(Receiver receiver, Exception e);
@Override
protected void onPreExecute() {
super.onPreExecute();
synchronized (connectorLock) {
CommCareTaskConnector<Receiver> connector = getConnector();
if (connector != null) {
connector.startBlockingForTask(getTaskId());
}
}
}
@Override
protected void onProgressUpdate(Progress... values) {
super.onProgressUpdate(values);
synchronized (connectorLock) {
CommCareTaskConnector<Receiver> connector = getConnector(false);
if (connector != null) {
this.deliverUpdate(connector.getReceiver(), values);
}
}
}
private CommCareTaskConnector<Receiver> getConnector() {
return getConnector(true);
}
private CommCareTaskConnector<Receiver> getConnector(boolean required) {
//So there might have been some transfer of ownership happening.
//We wanna hold off on anything that requires the connector
//until there is one present, up until some specified limit
long requested = System.currentTimeMillis();
while (System.currentTimeMillis() - requested < ALLOWABLE_CONNECTOR_ACQUISITION_DELAY) {
//See if we've gotten a connector
synchronized (connectorLock) {
if (connector != null) {
return connector;
} else {
//We might just be updating progress or something
if (!required) {
return null;
}
}
}
}
//Otherwise we're orphaned and we should cancel
synchronized (connectorLock) {
//Ok, so check one last time (so we can lock the connection still and prevent
//something from connecting in the mean time
if (connector != null) {
return connector;
}
if (this.getStatus() == AsyncTask.Status.RUNNING && taskId != -1) {
// If the connector is null and the task is associated with a
// dialog/activity (i.e. task id != -1) then cancel because the
// task isn't expected to live past the associated
// dialog/activity
Log.d(TAG, "Cancelling " + TAG + " because the activity it was connected to is gone");
this.cancel(false);
}
return null;
}
}
public void connect(CommCareTaskConnector<Receiver> connector) {
synchronized (connectorLock) {
//TODO: Maybe notify the old thing that we're disconnecting?
this.connector = connector;
this.connector.connectTask(this);
}
}
/**
* Attempts to kill long running processes prematurely in the task
*/
public void tryAbort() {
}
protected void transitionPhase(int newTaskId) {
synchronized (connectorLock) {
if (newTaskId != taskId) {
CommCareTaskConnector<Receiver> connector = this.getConnector(true);
if (connector != null) {
connector.stopBlockingForTask(taskId);
connector.startBlockingForTask(newTaskId);
this.taskId = newTaskId;
}
}
}
}
public int getTaskId() {
return taskId;
}
/**
* Disconnect this task from its current connector.
* Used when the user interface has to give up its hook to the
* current task.
*/
public void disconnect() {
synchronized (connectorLock) {
connector = null;
}
}
}