/**
*
*/
package org.commcare.android.tasks.templates;
import android.os.AsyncTask;
/**
* @author ctsims
*
*/
public abstract class CommCareTask<A, B, C, R> extends AsyncTask<A, B, C> {
public static final int GENERIC_TASK_ID = 32;
private Object connectorLock = new Object();
private CommCareTaskConnector<R> connector;
private boolean isLostOrphan = false;
private Exception unknownError;
protected int taskId = GENERIC_TASK_ID;
public CommCareTask() {
}
/* (non-Javadoc)
* @see android.os.AsyncTask#doInBackground(Params[])
*/
@Override
protected final C doInBackground(A... params) {
//Never have to wrap the entirety of your task.
try {
return doTaskBackground(params);
} catch(Exception e) {
e.printStackTrace();
unknownError = e;
return null;
}
}
protected abstract C doTaskBackground(A... params);
/* (non-Javadoc)
* @see android.os.AsyncTask#onCancelled()
*/
@Override
protected void onCancelled() {
super.onCancelled();
synchronized(connectorLock) {
CommCareTaskConnector<R> connector = getConnector();
if(connector == null) {
//TODO: FailedConnection
return;
}
connector.startTaskTransition();
connector.stopBlockingForTask(getTaskId());
connector.taskCancelled(getTaskId());
connector.stopTaskTransition();
}
}
/* (non-Javadoc)
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
@Override
protected void onPostExecute(C result) {
super.onPostExecute(result);
synchronized(connectorLock) {
//TODO: extend blocking here?
CommCareTaskConnector<R> connector = getConnector();
if(connector == null) {
//TODO: FailedConnection
return;
}
connector.startTaskTransition();
connector.stopBlockingForTask(getTaskId());
if(unknownError != null) {
deliverError(connector.getReceiver(), unknownError);
return;
}
this.deliverResult(connector.getReceiver(), result);
connector.stopTaskTransition();
}
}
protected abstract void deliverResult(R receiver, C result);
protected abstract void deliverUpdate(R receiver, B... update);
protected abstract void deliverError(R receiver, Exception e);
/* (non-Javadoc)
* @see android.os.AsyncTask#onPreExecute()
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
synchronized(connectorLock) {
CommCareTaskConnector<R> connector = getConnector();
if(connector == null) {
//TODO: FailedConnection
return;
}
connector.startBlockingForTask(getTaskId());
}
}
/* (non-Javadoc)
* @see android.os.AsyncTask#onProgressUpdate(Progress[])
*/
@Override
protected void onProgressUpdate(B... values) {
super.onProgressUpdate(values);
synchronized(connectorLock) {
CommCareTaskConnector<R> connector = getConnector(false);
if(connector != null) {
this.deliverUpdate(connector.getReceiver(), values);
}
}
}
//Wait for 2 seconds for something to reconnnect for now (very high)
private int allowableDelay = 2000;
protected CommCareTaskConnector<R> getConnector() {
return getConnector(true);
}
protected CommCareTaskConnector<R> 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 < allowableDelay) {
//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; }
//Otherwise, cancel if possible
if(this.getStatus() == AsyncTask.Status.RUNNING) {
this.cancel(false);
}
//Mark this as a lost orphan
this.isLostOrphan = true;
//and return null;
return null;
}
}
public void connect(CommCareTaskConnector<R> connector) {
synchronized(connectorLock) {
//TODO: Maybe notify the old thing that we're disconnecting?
this.connector = connector;
this.connector.connectTask(this);
}
}
protected void transitionPhase(int newTaskId) {
synchronized(connectorLock) {
CommCareTaskConnector<R> connector = this.getConnector(true);
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;
}
}
}