/* Copyright (c) 2009 Matthias Käppler
*
* 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.github.droidfu.concurrent;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.view.Window;
import com.github.droidfu.DroidFuApplication;
import com.github.droidfu.activities.BetterActivity;
/**
* Works in a similar way to AsyncTask but provides extra functionality.
*
* 1) It keeps track of the active instance of each Context, ensuring that the
* correct instance is reported to. This is very useful if your Activity is
* forced into the background, or the user rotates his device.
*
* 2) A progress dialog is automatically shown. See useCustomDialog()
* disableDialog()
*
* 3) If an Exception is thrown from inside doInBackground, this is now handled
* by the handleError method.
*
* 4) You should now longer override onPreExecute(), doInBackground() and
* onPostExecute(), instead you should use before(), doCheckedInBackground() and
* after() respectively.
*
* These features require that the Application extends DroidFuApplication.
*
* @param <ParameterT>
* @param <ProgressT>
* @param <ReturnT>
*/
public abstract class BetterAsyncTask<ParameterT, ProgressT, ReturnT> extends
AsyncTask<ParameterT, ProgressT, ReturnT> {
private final DroidFuApplication appContext;
private final boolean contextIsDroidFuActivity;
private Exception error;
private boolean isTitleProgressEnabled,
isTitleProgressIndeterminateEnabled = true;
private final String callerId;
private BetterAsyncTaskCallable<ParameterT, ProgressT, ReturnT> callable;
private int dialogId = 0;
/**
* Creates a new BetterAsyncTask who displays a progress dialog on the specified Context.
*
* @param context
*/
public BetterAsyncTask(Context context) {
if (!(context.getApplicationContext() instanceof DroidFuApplication)) {
throw new IllegalArgumentException(
"context bound to this task must be a DroidFu context (DroidFuApplication)");
}
this.appContext = (DroidFuApplication) context.getApplicationContext();
this.callerId = context.getClass().getCanonicalName();
this.contextIsDroidFuActivity = context instanceof BetterActivity;
appContext.setActiveContext(callerId, context);
if (contextIsDroidFuActivity) {
int windowFeatures = ((BetterActivity) context).getWindowFeatures();
if (Window.FEATURE_PROGRESS == (Window.FEATURE_PROGRESS & windowFeatures)) {
this.isTitleProgressEnabled = true;
} else if (Window.FEATURE_INDETERMINATE_PROGRESS == (Window.FEATURE_INDETERMINATE_PROGRESS & windowFeatures)) {
this.isTitleProgressIndeterminateEnabled = true;
}
}
}
/**
* Gets the most recent instance of this Context.
* This may not be the Context used to construct this BetterAsyncTask as that Context might have been destroyed
* when a incoming call was received, or the user rotated the screen.
*
* @return The current Context, or null if the current Context has ended, and a new one has not spawned.
*/
protected Context getCallingContext() {
try {
Context caller = (Context) appContext.getActiveContext(callerId);
if (caller == null || !this.callerId.equals(caller.getClass().getCanonicalName())
|| (caller instanceof Activity && ((Activity) caller).isFinishing())) {
// the context that started this task has died and/or was
// replaced with a different one
return null;
}
return caller;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected final void onPreExecute() {
Context context = getCallingContext();
if (context == null) {
Log.d(BetterAsyncTask.class.getSimpleName(), "skipping pre-exec handler for task "
+ hashCode() + " (context is null)");
cancel(true);
return;
}
if (contextIsDroidFuActivity) {
Activity activity = (Activity) context;
if (dialogId > -1) {
activity.showDialog(dialogId);
}
if (isTitleProgressEnabled) {
activity.setProgressBarVisibility(true);
} else if (isTitleProgressIndeterminateEnabled) {
activity.setProgressBarIndeterminateVisibility(true);
}
}
before(context);
}
/**
* Override to run code in the UI thread before this Task is run.
*
* @param context
*/
protected void before(Context context) {
}
@Override
protected final ReturnT doInBackground(ParameterT... params) {
ReturnT result = null;
Context context = getCallingContext();
try {
result = doCheckedInBackground(context, params);
} catch (Exception e) {
this.error = e;
}
return result;
}
/**
* Override to perform computation in a background thread
*
* @param context
* @param params
* @return
* @throws Exception
*/
protected ReturnT doCheckedInBackground(Context context, ParameterT... params) throws Exception {
if (callable != null) {
return callable.call(this);
}
return null;
}
/**
* Runs in the UI thread if there was an exception throw from doCheckedInBackground
*
* @param context The most recent instance of the Context that executed this BetterAsyncTask
* @param error The thrown exception.
*/
protected abstract void handleError(Context context, Exception error);
@Override
protected final void onPostExecute(ReturnT result) {
Context context = getCallingContext();
if (context == null) {
Log.d(BetterAsyncTask.class.getSimpleName(), "skipping post-exec handler for task "
+ hashCode() + " (context is null)");
return;
}
if (contextIsDroidFuActivity) {
Activity activity = (Activity) context;
if (dialogId > -1) {
activity.removeDialog(dialogId);
}
if (isTitleProgressEnabled) {
activity.setProgressBarVisibility(false);
} else if (isTitleProgressIndeterminateEnabled) {
activity.setProgressBarIndeterminateVisibility(false);
}
}
if (failed()) {
handleError(context, error);
} else {
after(context, result);
}
}
/**
* A replacement for onPostExecute. Runs in the UI thread after doCheckedInBackground returns.
*
* @param context The most recent instance of the Context that executed this BetterAsyncTask
* @param result The result returned from doCheckedInBackground
*/
protected abstract void after(Context context, ReturnT result);
/**
* Has an exception been thrown inside doCheckedInBackground()
* @return
*/
public boolean failed() {
return error != null;
}
/**
* Use a BetterAsyncTaskCallable instead of overriding doCheckedInBackground()
*
* @param callable
*/
public void setCallable(BetterAsyncTaskCallable<ParameterT, ProgressT, ReturnT> callable) {
this.callable = callable;
}
/**
* Use a custom resource ID for the progress dialog
*
* @param dialogId
*/
public void useCustomDialog(int dialogId) {
this.dialogId = dialogId;
}
/**
* Disable the display of a dialog during the execution of this task.
*/
public void disableDialog() {
this.dialogId = -1;
}
}