com.google.io.accessibility.util.IncrementalAsyncTaskLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.google.io.accessibility.util.IncrementalAsyncTaskLoader.java

Source

package com.google.io.accessibility.util;

/*
 * 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.
 */

import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.content.Loader;
import android.support.v4.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 IncrementalAsyncTaskLoader<B, C, D> extends Loader<D> {
    static final String TAG = IncrementalAsyncTaskLoader.class.getSimpleName();

    static final boolean DEBUG = false;

    final class LoadTask extends AsyncTask<B, C, D> implements Runnable {

        D result;
        boolean waiting;

        private CountDownLatch done = new CountDownLatch(1);

        void dispatchPublishProgress(C... data) {
            publishProgress(data);
        }

        /* Runs on a worker thread */
        @Override
        protected D doInBackground(B... params) {
            if (DEBUG)
                Log.v(TAG, this + " >>> doInBackground");
            result = IncrementalAsyncTaskLoader.this.onLoadInBackground();
            if (DEBUG)
                Log.v(TAG, this + "  <<< doInBackground");
            return result;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            dispatchOnPreExecute();
        }

        @Override
        protected void onProgressUpdate(C... values) {
            super.onProgressUpdate(values);
            dispatchOnProgressUpdate(values);
        }

        /* Runs on the UI thread */
        @Override
        protected void onPostExecute(D data) {
            if (DEBUG)
                Log.v(TAG, this + " onPostExecute");
            try {
                IncrementalAsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
                dispatchOnPostExecute(data);
            } finally {
                done.countDown();
            }
        }

        @Override
        protected void onCancelled() {
            if (DEBUG)
                Log.v(TAG, this + " onCancelled");
            try {
                IncrementalAsyncTaskLoader.this.dispatchOnCancelled(this, result);
            } finally {
                done.countDown();
            }
        }

        @Override
        public void run() {
            waiting = false;
            IncrementalAsyncTaskLoader.this.executePendingTask();
        }
    }

    volatile LoadTask mTask;
    volatile LoadTask mCancellingTask;
    volatile IncrementalAsyncTaskLoaderCallbacks<C, D> mCallbacks;

    long mUpdateThrottle;
    long mLastLoadCompleteTime = -10000;
    Handler mHandler;

    public IncrementalAsyncTaskLoader(Context context, IncrementalAsyncTaskLoaderCallbacks<C, D> callbacks) {
        super(context);
        this.mCallbacks = callbacks;
    }

    protected void publishProgress(C... data) {
        mTask.dispatchPublishProgress(data);
    }

    /**
     * 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((B[]) null);
            // XXX TO DO: use reflection to call this version.
            //mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
        }
    }

    void dispatchOnPreExecute() {
        mCallbacks.onPreExecute();
    }

    void dispatchOnProgressUpdate(C... progressData) {
        mCallbacks.onProgressUpdate(progressData[0]);
    }

    void dispatchOnPostExecute(D data) {
        mCallbacks.onPostExecute(data);
    }

    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();
        }
    }
}