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