package com.manuelmaly.hn.task; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncTask; import android.support.v4.content.LocalBroadcastManager; import com.manuelmaly.hn.App; import com.manuelmaly.hn.reuse.CancelableRunnable; import com.manuelmaly.hn.server.IAPICommand; import com.manuelmaly.hn.task.ITaskFinishedHandler.TaskResultCode; import com.manuelmaly.hn.util.Run; import java.io.Serializable; import java.lang.ref.SoftReference; /** * Generic base for tasks performed asynchronously. Unlike {@link AsyncTask}, * its on-finished-notification will be passed to every Activity instance which * has registered (listeners are notified via an intent sent to * {@link LocalBroadcastManager}). Meaning, there will be no Zombie tasks * performing stuff for nothing (e.g. because their callback Activity has been * destroyed because of orientation change). * * @author manuelmaly * @param <T> * result type */ public abstract class BaseTask<T extends Serializable> implements Runnable { public static final String BROADCAST_INTENT_EXTRA_ERROR = "error"; public static final String BROADCAST_INTENT_EXTRA_RESULT = "result"; protected String mNotificationBroadcastIntentID; protected T mResult; protected int mErrorCode; protected boolean mIsRunning; protected CancelableRunnable mTaskRunnable; protected int mTaskCode; protected Object mTag; public BaseTask(String notificationBroadcastIntentID, int taskCode) { mNotificationBroadcastIntentID = notificationBroadcastIntentID; mTaskCode = taskCode; } protected void startInBackground() { Run.inBackground(this); } /** * The broadcast will be received by listeners on the main thread * implicitly. */ public void notifyFinished(int errorCode, Serializable result) { Intent broadcastIntent = new Intent(mNotificationBroadcastIntentID); broadcastIntent.putExtra(BROADCAST_INTENT_EXTRA_ERROR, errorCode); broadcastIntent.putExtra(BROADCAST_INTENT_EXTRA_RESULT, result); LocalBroadcastManager.getInstance(App.getInstance()).sendBroadcast(broadcastIntent); } /** * * @param tag */ public void setTag(Object tag) { mTag = tag; } /** * Registers the given {@link BroadcastReceiver} to this task's * finished-notification. * * @param receiver */ public void registerForFinishedNotification(BroadcastReceiver receiver) { IntentFilter filter = new IntentFilter(mNotificationBroadcastIntentID); LocalBroadcastManager.getInstance(App.getInstance()).registerReceiver(receiver, filter); } /** * Schedules behaviour to be executed when this task has finished, for the * given Activity. * * @param activity * @param finishedHandler * @param resultClazz */ public void setOnFinishedHandler(Activity activity, ITaskFinishedHandler<T> finishedHandler, final Class<T> resultClazz) { final SoftReference<Activity> activityRef = new SoftReference<Activity>(activity); final SoftReference<ITaskFinishedHandler<T>> finishedHandlerRef = new SoftReference<ITaskFinishedHandler<T>>( finishedHandler); BroadcastReceiver finishedListener = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { LocalBroadcastManager.getInstance(App.getInstance()).unregisterReceiver(this); if (activityRef == null || activityRef.get() == null || finishedHandlerRef == null || finishedHandlerRef.get() == null) return; // Make hard references until the end of processing, so we don't // lose those objects: Activity activity = activityRef.get(); final ITaskFinishedHandler<T> finishedHandler = finishedHandlerRef.get(); int lowLevelErrorCode = intent.getIntExtra(BaseTask.BROADCAST_INTENT_EXTRA_ERROR, IAPICommand.ERROR_NONE); final int errorCode; final T result; Serializable rawResult = intent.getSerializableExtra(BaseTask.BROADCAST_INTENT_EXTRA_RESULT); if (resultClazz.isInstance(rawResult)) { result = resultClazz.cast(rawResult); errorCode = lowLevelErrorCode; } else { result = null; if (lowLevelErrorCode == IAPICommand.ERROR_NONE) { // We have no error so far, but cannot cast the result data: errorCode = IAPICommand.ERROR_UNKNOWN; } else { errorCode = lowLevelErrorCode; } } Runnable r = new Runnable() { public void run() { finishedHandler .onTaskFinished(mTaskCode, TaskResultCode.fromErrorCode(errorCode), result, mTag); } }; Run.onUiThread(r, activity); } }; this.registerForFinishedNotification(finishedListener); } @Override public void run() { mIsRunning = true; mTaskRunnable = getTask(); mTaskRunnable.run(); mIsRunning = false; notifyFinished(mErrorCode, mResult); } public boolean isRunning() { return mIsRunning; } public T getResult() { return mResult; } public int getErrorCode() { return mErrorCode; } public void cancel() { Run.inBackground(new Runnable() { @Override public void run() { if (mTaskRunnable != null) mTaskRunnable.cancel(); } }); } public abstract CancelableRunnable getTask(); }