Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import android.annotation.UnsupportedAppUsage;
     20 import android.os.AsyncTask;
     21 import android.os.Handler;
     22 import android.os.OperationCanceledException;
     23 import android.os.SystemClock;
     24 import android.util.Log;
     25 import android.util.TimeUtils;
     26 
     27 import java.io.FileDescriptor;
     28 import java.io.PrintWriter;
     29 import java.util.concurrent.CountDownLatch;
     30 import java.util.concurrent.Executor;
     31 
     32 /**
     33  * Abstract Loader that provides an {@link AsyncTask} to do the work.  See
     34  * {@link Loader} and {@link android.app.LoaderManager} for more details.
     35  *
     36  * <p>Here is an example implementation of an AsyncTaskLoader subclass that
     37  * loads the currently installed applications from the package manager.  This
     38  * implementation takes care of retrieving the application labels and sorting
     39  * its result set from them, monitoring for changes to the installed
     40  * applications, and rebuilding the list when a change in configuration requires
     41  * this (such as a locale change).
     42  *
     43  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
     44  *      loader}
     45  *
     46  * <p>An example implementation of a fragment that uses the above loader to show
     47  * the currently installed applications in a list is below.
     48  *
     49  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
     50  *      fragment}
     51  *
     52  * @param <D> the data type to be loaded.
     53  *
     54  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
     55  *      {@link android.support.v4.content.AsyncTaskLoader}
     56  */
     57 @Deprecated
     58 public abstract class AsyncTaskLoader<D> extends Loader<D> {
     59     static final String TAG = "AsyncTaskLoader";
     60     static final boolean DEBUG = false;
     61 
     62     final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
     63         private final CountDownLatch mDone = new CountDownLatch(1);
     64 
     65         // Set to true to indicate that the task has been posted to a handler for
     66         // execution at a later time.  Used to throttle updates.
     67         boolean waiting;
     68 
     69         /* Runs on a worker thread */
     70         @Override
     71         protected D doInBackground(Void... params) {
     72             if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
     73             try {
     74                 D data = AsyncTaskLoader.this.onLoadInBackground();
     75                 if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
     76                 return data;
     77             } catch (OperationCanceledException ex) {
     78                 if (!isCancelled()) {
     79                     // onLoadInBackground threw a canceled exception spuriously.
     80                     // This is problematic because it means that the LoaderManager did not
     81                     // cancel the Loader itself and still expects to receive a result.
     82                     // Additionally, the Loader's own state will not have been updated to
     83                     // reflect the fact that the task was being canceled.
     84                     // So we treat this case as an unhandled exception.
     85                     throw ex;
     86                 }
     87                 if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
     88                 return null;
     89             }
     90         }
     91 
     92         /* Runs on the UI thread */
     93         @Override
     94         protected void onPostExecute(D data) {
     95             if (DEBUG) Log.v(TAG, this + " onPostExecute");
     96             try {
     97                 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
     98             } finally {
     99                 mDone.countDown();
    100             }
    101         }
    102 
    103         /* Runs on the UI thread */
    104         @Override
    105         protected void onCancelled(D data) {
    106             if (DEBUG) Log.v(TAG, this + " onCancelled");
    107             try {
    108                 AsyncTaskLoader.this.dispatchOnCancelled(this, data);
    109             } finally {
    110                 mDone.countDown();
    111             }
    112         }
    113 
    114         /* Runs on the UI thread, when the waiting task is posted to a handler.
    115          * This method is only executed when task execution was deferred (waiting was true). */
    116         @Override
    117         public void run() {
    118             waiting = false;
    119             AsyncTaskLoader.this.executePendingTask();
    120         }
    121 
    122         /* Used for testing purposes to wait for the task to complete. */
    123         public void waitForLoader() {
    124             try {
    125                 mDone.await();
    126             } catch (InterruptedException e) {
    127                 // Ignore
    128             }
    129         }
    130     }
    131 
    132     @UnsupportedAppUsage
    133     private final Executor mExecutor;
    134 
    135     volatile LoadTask mTask;
    136     volatile LoadTask mCancellingTask;
    137 
    138     long mUpdateThrottle;
    139     long mLastLoadCompleteTime = -10000;
    140     Handler mHandler;
    141 
    142     public AsyncTaskLoader(Context context) {
    143         this(context, AsyncTask.THREAD_POOL_EXECUTOR);
    144     }
    145 
    146     /** {@hide} */
    147     public AsyncTaskLoader(Context context, Executor executor) {
    148         super(context);
    149         mExecutor = executor;
    150     }
    151 
    152     /**
    153      * Set amount to throttle updates by.  This is the minimum time from
    154      * when the last {@link #loadInBackground()} call has completed until
    155      * a new load is scheduled.
    156      *
    157      * @param delayMS Amount of delay, in milliseconds.
    158      */
    159     public void setUpdateThrottle(long delayMS) {
    160         mUpdateThrottle = delayMS;
    161         if (delayMS != 0) {
    162             mHandler = new Handler();
    163         }
    164     }
    165 
    166     @Override
    167     protected void onForceLoad() {
    168         super.onForceLoad();
    169         cancelLoad();
    170         mTask = new LoadTask();
    171         if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
    172         executePendingTask();
    173     }
    174 
    175     @Override
    176     protected boolean onCancelLoad() {
    177         if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask);
    178         if (mTask != null) {
    179             if (!mStarted) {
    180                 mContentChanged = true;
    181             }
    182             if (mCancellingTask != null) {
    183                 // There was a pending task already waiting for a previous
    184                 // one being canceled; just drop it.
    185                 if (DEBUG) Log.v(TAG,
    186                         "cancelLoad: still waiting for cancelled task; dropping next");
    187                 if (mTask.waiting) {
    188                     mTask.waiting = false;
    189                     mHandler.removeCallbacks(mTask);
    190                 }
    191                 mTask = null;
    192                 return false;
    193             } else if (mTask.waiting) {
    194                 // There is a task, but it is waiting for the time it should
    195                 // execute.  We can just toss it.
    196                 if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
    197                 mTask.waiting = false;
    198                 mHandler.removeCallbacks(mTask);
    199                 mTask = null;
    200                 return false;
    201             } else {
    202                 boolean cancelled = mTask.cancel(false);
    203                 if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
    204                 if (cancelled) {
    205                     mCancellingTask = mTask;
    206                     cancelLoadInBackground();
    207                 }
    208                 mTask = null;
    209                 return cancelled;
    210             }
    211         }
    212         return false;
    213     }
    214 
    215     /**
    216      * Called if the task was canceled before it was completed.  Gives the class a chance
    217      * to clean up post-cancellation and to properly dispose of the result.
    218      *
    219      * @param data The value that was returned by {@link #loadInBackground}, or null
    220      * if the task threw {@link OperationCanceledException}.
    221      */
    222     public void onCanceled(D data) {
    223     }
    224 
    225     void executePendingTask() {
    226         if (mCancellingTask == null && mTask != null) {
    227             if (mTask.waiting) {
    228                 mTask.waiting = false;
    229                 mHandler.removeCallbacks(mTask);
    230             }
    231             if (mUpdateThrottle > 0) {
    232                 long now = SystemClock.uptimeMillis();
    233                 if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
    234                     // Not yet time to do another load.
    235                     if (DEBUG) Log.v(TAG, "Waiting until "
    236                             + (mLastLoadCompleteTime+mUpdateThrottle)
    237                             + " to execute: " + mTask);
    238                     mTask.waiting = true;
    239                     mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
    240                     return;
    241                 }
    242             }
    243             if (DEBUG) Log.v(TAG, "Executing: " + mTask);
    244             mTask.executeOnExecutor(mExecutor, (Void[]) null);
    245         }
    246     }
    247 
    248     void dispatchOnCancelled(LoadTask task, D data) {
    249         onCanceled(data);
    250         if (mCancellingTask == task) {
    251             if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
    252             rollbackContentChanged();
    253             mLastLoadCompleteTime = SystemClock.uptimeMillis();
    254             mCancellingTask = null;
    255             if (DEBUG) Log.v(TAG, "Delivering cancellation");
    256             deliverCancellation();
    257             executePendingTask();
    258         }
    259     }
    260 
    261     void dispatchOnLoadComplete(LoadTask task, D data) {
    262         if (mTask != task) {
    263             if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
    264             dispatchOnCancelled(task, data);
    265         } else {
    266             if (isAbandoned()) {
    267                 // This cursor has been abandoned; just cancel the new data.
    268                 onCanceled(data);
    269             } else {
    270                 commitContentChanged();
    271                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
    272                 mTask = null;
    273                 if (DEBUG) Log.v(TAG, "Delivering result");
    274                 deliverResult(data);
    275             }
    276         }
    277     }
    278 
    279     /**
    280      * Called on a worker thread to perform the actual load and to return
    281      * the result of the load operation.
    282      *
    283      * Implementations should not deliver the result directly, but should return them
    284      * from this method, which will eventually end up calling {@link #deliverResult} on
    285      * the UI thread.  If implementations need to process the results on the UI thread
    286      * they may override {@link #deliverResult} and do so there.
    287      *
    288      * To support cancellation, this method should periodically check the value of
    289      * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
    290      * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
    291      * directly instead of polling {@link #isLoadInBackgroundCanceled}.
    292      *
    293      * When the load is canceled, this method may either return normally or throw
    294      * {@link OperationCanceledException}.  In either case, the {@link Loader} will
    295      * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
    296      * result object, if any.
    297      *
    298      * @return The result of the load operation.
    299      *
    300      * @throws OperationCanceledException if the load is canceled during execution.
    301      *
    302      * @see #isLoadInBackgroundCanceled
    303      * @see #cancelLoadInBackground
    304      * @see #onCanceled
    305      */
    306     public abstract D loadInBackground();
    307 
    308     /**
    309      * Calls {@link #loadInBackground()}.
    310      *
    311      * This method is reserved for use by the loader framework.
    312      * Subclasses should override {@link #loadInBackground} instead of this method.
    313      *
    314      * @return The result of the load operation.
    315      *
    316      * @throws OperationCanceledException if the load is canceled during execution.
    317      *
    318      * @see #loadInBackground
    319      */
    320     protected D onLoadInBackground() {
    321         return loadInBackground();
    322     }
    323 
    324     /**
    325      * Called on the main thread to abort a load in progress.
    326      *
    327      * Override this method to abort the current invocation of {@link #loadInBackground}
    328      * that is running in the background on a worker thread.
    329      *
    330      * This method should do nothing if {@link #loadInBackground} has not started
    331      * running or if it has already finished.
    332      *
    333      * @see #loadInBackground
    334      */
    335     public void cancelLoadInBackground() {
    336     }
    337 
    338     /**
    339      * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
    340      *
    341      * @return True if the current invocation of {@link #loadInBackground} is being canceled.
    342      *
    343      * @see #loadInBackground
    344      */
    345     public boolean isLoadInBackgroundCanceled() {
    346         return mCancellingTask != null;
    347     }
    348 
    349     /**
    350      * Locks the current thread until the loader completes the current load
    351      * operation. Returns immediately if there is no load operation running.
    352      * Should not be called from the UI thread: calling it from the UI
    353      * thread would cause a deadlock.
    354      * <p>
    355      * Use for testing only.  <b>Never</b> call this from a UI thread.
    356      *
    357      * @hide
    358      */
    359     @UnsupportedAppUsage
    360     public void waitForLoader() {
    361         LoadTask task = mTask;
    362         if (task != null) {
    363             task.waitForLoader();
    364         }
    365     }
    366 
    367     @Override
    368     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    369         super.dump(prefix, fd, writer, args);
    370         if (mTask != null) {
    371             writer.print(prefix); writer.print("mTask="); writer.print(mTask);
    372                     writer.print(" waiting="); writer.println(mTask.waiting);
    373         }
    374         if (mCancellingTask != null) {
    375             writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
    376                     writer.print(" waiting="); writer.println(mCancellingTask.waiting);
    377         }
    378         if (mUpdateThrottle != 0) {
    379             writer.print(prefix); writer.print("mUpdateThrottle=");
    380                     TimeUtils.formatDuration(mUpdateThrottle, writer);
    381                     writer.print(" mLastLoadCompleteTime=");
    382                     TimeUtils.formatDuration(mLastLoadCompleteTime,
    383                             SystemClock.uptimeMillis(), writer);
    384                     writer.println();
    385         }
    386     }
    387 }
    388