--- /dev/null
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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 android.support.v2.content;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v2.util.TimeUtils;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Static library support version of the framework's {@link android.content.AsyncTaskLoader}.
+ * Used to write apps that run on platforms prior to Android 3.0. When running
+ * on Android 3.0 or above, this implementation is still used; it does not try
+ * to switch to the framework's implementation. See the framework SDK
+ * documentation for a class overview.
+ */
+public abstract class AsyncTaskLoader<D> extends Loader<D> {
+ static final String TAG = "AsyncTaskLoader";
+ static final boolean DEBUG = false;
+
+ final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
+
+ D result;
+ boolean waiting;
+
+ private CountDownLatch done = new CountDownLatch(1);
+
+ /* Runs on a worker thread */
+ @Override
+ protected D doInBackground(Void... params) {
+ if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
+ result = AsyncTaskLoader.this.onLoadInBackground();
+ if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
+ return result;
+ }
+
+ /* Runs on the UI thread */
+ @Override
+ protected void onPostExecute(D data) {
+ if (DEBUG) Log.v(TAG, this + " onPostExecute");
+ try {
+ AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
+ } finally {
+ done.countDown();
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ if (DEBUG) Log.v(TAG, this + " onCancelled");
+ try {
+ AsyncTaskLoader.this.dispatchOnCancelled(this, result);
+ } finally {
+ done.countDown();
+ }
+ }
+
+ @Override
+ public void run() {
+ waiting = false;
+ AsyncTaskLoader.this.executePendingTask();
+ }
+ }
+
+ volatile LoadTask mTask;
+ volatile LoadTask mCancellingTask;
+
+ long mUpdateThrottle;
+ long mLastLoadCompleteTime = -10000;
+ Handler mHandler;
+
+ public AsyncTaskLoader(Context context) {
+ super(context);
+ }
+
+ /**
+ * Set amount to throttle updates by. This is the minimum time from
+ * when the last {@link #onLoadInBackground()} call has completed until
+ * a new load is scheduled.
+ *
+ * @param delayMS Amount of delay, in milliseconds.
+ */
+ public void setUpdateThrottle(long delayMS) {
+ mUpdateThrottle = delayMS;
+ if (delayMS != 0) {
+ mHandler = new Handler();
+ }
+ }
+
+ @Override
+ protected void onForceLoad() {
+ super.onForceLoad();
+ cancelLoad();
+ mTask = new LoadTask();
+ if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
+ executePendingTask();
+ }
+
+ /**
+ * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
+ * for more info. Must be called on the main thread of the process.
+ *
+ * <p>Cancelling is not an immediate operation, since the load is performed
+ * in a background thread. If there is currently a load in progress, this
+ * method requests that the load be cancelled, and notes this is the case;
+ * once the background thread has completed its work its remaining state
+ * will be cleared. If another load request comes in during this time,
+ * it will be held until the cancelled load is complete.
+ *
+ * @return Returns <tt>false</tt> if the task could not be cancelled,
+ * typically because it has already completed normally, or
+ * because {@link #startLoading()} hasn't been called; returns
+ * <tt>true</tt> otherwise.
+ */
+ public boolean cancelLoad() {
+ if (DEBUG) Log.v(TAG, "cancelLoad: mTask=" + mTask);
+ if (mTask != null) {
+ if (mCancellingTask != null) {
+ // There was a pending task already waiting for a previous
+ // one being canceled; just drop it.
+ if (DEBUG) Log.v(TAG,
+ "cancelLoad: still waiting for cancelled task; dropping next");
+ if (mTask.waiting) {
+ mTask.waiting = false;
+ mHandler.removeCallbacks(mTask);
+ }
+ mTask = null;
+ return false;
+ } else if (mTask.waiting) {
+ // There is a task, but it is waiting for the time it should
+ // execute. We can just toss it.
+ if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
+ mTask.waiting = false;
+ mHandler.removeCallbacks(mTask);
+ mTask = null;
+ return false;
+ } else {
+ boolean cancelled = mTask.cancel(false);
+ if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
+ if (cancelled) {
+ mCancellingTask = mTask;
+ }
+ mTask = null;
+ return cancelled;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called if the task was canceled before it was completed. Gives the class a chance
+ * to properly dispose of the result.
+ */
+ public void onCanceled(D data) {
+ }
+
+ void executePendingTask() {
+ if (mCancellingTask == null && mTask != null) {
+ if (mTask.waiting) {
+ mTask.waiting = false;
+ mHandler.removeCallbacks(mTask);
+ }
+ if (mUpdateThrottle > 0) {
+ long now = SystemClock.uptimeMillis();
+ if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
+ // Not yet time to do another load.
+ if (DEBUG) Log.v(TAG, "Waiting until "
+ + (mLastLoadCompleteTime+mUpdateThrottle)
+ + " to execute: " + mTask);
+ mTask.waiting = true;
+ mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
+ return;
+ }
+ }
+ if (DEBUG) Log.v(TAG, "Executing: " + mTask);
+ mTask.execute((Void[]) null);
+ // XXX TO DO: use reflection to call this version.
+ //mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+ }
+
+ void dispatchOnCancelled(LoadTask task, D data) {
+ onCanceled(data);
+ if (mCancellingTask == task) {
+ if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
+ mLastLoadCompleteTime = SystemClock.uptimeMillis();
+ mCancellingTask = null;
+ executePendingTask();
+ }
+ }
+
+ void dispatchOnLoadComplete(LoadTask task, D data) {
+ if (mTask != task) {
+ if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
+ dispatchOnCancelled(task, data);
+ } else {
+ if (isAbandoned()) {
+ // This cursor has been abandoned; just cancel the new data.
+ onCanceled(data);
+ } else {
+ mLastLoadCompleteTime = SystemClock.uptimeMillis();
+ mTask = null;
+ if (DEBUG) Log.v(TAG, "Delivering result");
+ deliverResult(data);
+ }
+ }
+ }
+
+ /**
+ */
+ public abstract D loadInBackground();
+
+ /**
+ * Called on a worker thread to perform the actual load. Implementations should not deliver the
+ * result directly, but should return them from this method, which will eventually end up
+ * calling {@link #deliverResult} on the UI thread. If implementations need to process
+ * the results on the UI thread they may override {@link #deliverResult} and do so
+ * there.
+ *
+ * @return Implementations must return the result of their load operation.
+ */
+ protected D onLoadInBackground() {
+ return loadInBackground();
+ }
+
+ /**
+ * Locks the current thread until the loader completes the current load
+ * operation. Returns immediately if there is no load operation running.
+ * Should not be called from the UI thread: calling it from the UI
+ * thread would cause a deadlock.
+ * <p>
+ * Use for testing only. <b>Never</b> call this from a UI thread.
+ *
+ * @hide
+ */
+ public void waitForLoader() {
+ LoadTask task = mTask;
+ if (task != null) {
+ try {
+ task.done.await();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ if (mTask != null) {
+ writer.print(prefix); writer.print("mTask="); writer.print(mTask);
+ writer.print(" waiting="); writer.println(mTask.waiting);
+ }
+ if (mCancellingTask != null) {
+ writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
+ writer.print(" waiting="); writer.println(mCancellingTask.waiting);
+ }
+ if (mUpdateThrottle != 0) {
+ writer.print(prefix); writer.print("mUpdateThrottle=");
+ TimeUtils.formatDuration(mUpdateThrottle, writer);
+ writer.print(" mLastLoadCompleteTime=");
+ TimeUtils.formatDuration(mLastLoadCompleteTime,
+ SystemClock.uptimeMillis(), writer);
+ writer.println();
+ }
+ }
+}