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