blob: da5195292f783099f47449e25937713e6d984409 [file] [log] [blame]
Jeff Hamilton9911b7f2010-05-15 02:20:31 -05001/*
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
17package android.content;
18
19import android.os.AsyncTask;
Dianne Hackborn247fe742011-01-08 17:25:57 -080020import android.os.Handler;
21import android.os.SystemClock;
Dianne Hackborn540f86a2011-01-11 17:52:22 -080022import android.util.Slog;
Dianne Hackborn247fe742011-01-08 17:25:57 -080023import android.util.TimeUtils;
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -080024
Dianne Hackborn247fe742011-01-08 17:25:57 -080025import java.io.FileDescriptor;
26import java.io.PrintWriter;
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -080027import java.util.concurrent.CountDownLatch;
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050028
29/**
Dianne Hackborn9567a662011-04-19 18:44:03 -070030 * Abstract Loader that provides an {@link AsyncTask} to do the work. See
31 * {@link Loader} and {@link android.app.LoaderManager} for more details.
32 *
33 * <p>Here is an example implementation of an AsyncTaskLoader subclass that
34 * loads the currently installed applications from the package manager. This
35 * implementation takes care of retrieving the application labels and sorting
36 * its result set from them, monitoring for changes to the installed
37 * applications, and rebuilding the list when a change in configuration requires
38 * this (such as a locale change).
39 *
40 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
41 * loader}
42 *
43 * <p>An example implementation of a fragment that uses the above loader to show
44 * the currently installed applications in a list is below.
45 *
46 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
47 * fragment}
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -070048 *
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050049 * @param <D> the data type to be loaded.
50 */
51public abstract class AsyncTaskLoader<D> extends Loader<D> {
Dianne Hackborn540f86a2011-01-11 17:52:22 -080052 static final String TAG = "AsyncTaskLoader";
53 static final boolean DEBUG = false;
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -080054
Dianne Hackborn247fe742011-01-08 17:25:57 -080055 final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
Jeff Brownb19a71a2012-01-31 11:48:39 -080056 private final CountDownLatch mDone = new CountDownLatch(1);
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -070057
Jeff Brownb19a71a2012-01-31 11:48:39 -080058 // Set to true to indicate that the task has been posted to a handler for
59 // execution at a later time. Used to throttle updates.
Dianne Hackborn247fe742011-01-08 17:25:57 -080060 boolean waiting;
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -070061
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050062 /* Runs on a worker thread */
63 @Override
64 protected D doInBackground(Void... params) {
Dianne Hackborn540f86a2011-01-11 17:52:22 -080065 if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
Jeff Brownb19a71a2012-01-31 11:48:39 -080066 try {
67 D data = AsyncTaskLoader.this.onLoadInBackground();
68 if (DEBUG) Slog.v(TAG, this + " <<< doInBackground");
69 return data;
70 } catch (OperationCanceledException ex) {
71 if (!isCancelled()) {
72 // onLoadInBackground threw a canceled exception spuriously.
73 // This is problematic because it means that the LoaderManager did not
74 // cancel the Loader itself and still expects to receive a result.
75 // Additionally, the Loader's own state will not have been updated to
76 // reflect the fact that the task was being canceled.
77 // So we treat this case as an unhandled exception.
78 throw ex;
79 }
80 if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)");
81 return null;
82 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050083 }
84
85 /* Runs on the UI thread */
86 @Override
87 protected void onPostExecute(D data) {
Dianne Hackborn540f86a2011-01-11 17:52:22 -080088 if (DEBUG) Slog.v(TAG, this + " onPostExecute");
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -080089 try {
90 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
91 } finally {
Jeff Brownb19a71a2012-01-31 11:48:39 -080092 mDone.countDown();
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -080093 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050094 }
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -070095
Jeff Brownb19a71a2012-01-31 11:48:39 -080096 /* Runs on the UI thread */
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -070097 @Override
Jeff Brownb19a71a2012-01-31 11:48:39 -080098 protected void onCancelled(D data) {
Dianne Hackborn540f86a2011-01-11 17:52:22 -080099 if (DEBUG) Slog.v(TAG, this + " onCancelled");
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -0800100 try {
Jeff Brownb19a71a2012-01-31 11:48:39 -0800101 AsyncTaskLoader.this.dispatchOnCancelled(this, data);
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -0800102 } finally {
Jeff Brownb19a71a2012-01-31 11:48:39 -0800103 mDone.countDown();
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -0800104 }
Dianne Hackborn247fe742011-01-08 17:25:57 -0800105 }
106
Jeff Brownb19a71a2012-01-31 11:48:39 -0800107 /* Runs on the UI thread, when the waiting task is posted to a handler.
108 * This method is only executed when task execution was deferred (waiting was true). */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800109 @Override
110 public void run() {
111 waiting = false;
112 AsyncTaskLoader.this.executePendingTask();
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -0700113 }
Jeff Brownb19a71a2012-01-31 11:48:39 -0800114
115 /* Used for testing purposes to wait for the task to complete. */
116 public void waitForLoader() {
117 try {
118 mDone.await();
119 } catch (InterruptedException e) {
120 // Ignore
121 }
122 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500123 }
124
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -0800125 volatile LoadTask mTask;
Dianne Hackborn247fe742011-01-08 17:25:57 -0800126 volatile LoadTask mCancellingTask;
127
128 long mUpdateThrottle;
129 long mLastLoadCompleteTime = -10000;
130 Handler mHandler;
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500131
132 public AsyncTaskLoader(Context context) {
133 super(context);
134 }
135
Dianne Hackborn247fe742011-01-08 17:25:57 -0800136 /**
137 * Set amount to throttle updates by. This is the minimum time from
Jeff Brownb19a71a2012-01-31 11:48:39 -0800138 * when the last {@link #loadInBackground()} call has completed until
Dianne Hackborn247fe742011-01-08 17:25:57 -0800139 * a new load is scheduled.
140 *
141 * @param delayMS Amount of delay, in milliseconds.
142 */
143 public void setUpdateThrottle(long delayMS) {
144 mUpdateThrottle = delayMS;
145 if (delayMS != 0) {
146 mHandler = new Handler();
147 }
148 }
149
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500150 @Override
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800151 protected void onForceLoad() {
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800152 super.onForceLoad();
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -0700153 cancelLoad();
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500154 mTask = new LoadTask();
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800155 if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
Dianne Hackborn247fe742011-01-08 17:25:57 -0800156 executePendingTask();
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500157 }
158
Jeff Brownb19a71a2012-01-31 11:48:39 -0800159 @Override
160 protected boolean onCancelLoad() {
161 if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500162 if (mTask != null) {
Dianne Hackborn247fe742011-01-08 17:25:57 -0800163 if (mCancellingTask != null) {
164 // There was a pending task already waiting for a previous
165 // one being canceled; just drop it.
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800166 if (DEBUG) Slog.v(TAG,
167 "cancelLoad: still waiting for cancelled task; dropping next");
Dianne Hackborn247fe742011-01-08 17:25:57 -0800168 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.
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800177 if (DEBUG) Slog.v(TAG, "cancelLoad: task is waiting, dropping it");
Dianne Hackborn247fe742011-01-08 17:25:57 -0800178 mTask.waiting = false;
179 mHandler.removeCallbacks(mTask);
180 mTask = null;
181 return false;
182 } else {
183 boolean cancelled = mTask.cancel(false);
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800184 if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
Dianne Hackborn247fe742011-01-08 17:25:57 -0800185 if (cancelled) {
186 mCancellingTask = mTask;
Jeff Brownb19a71a2012-01-31 11:48:39 -0800187 cancelLoadInBackground();
Dianne Hackborn247fe742011-01-08 17:25:57 -0800188 }
189 mTask = null;
190 return cancelled;
191 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500192 }
193 return false;
194 }
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -0700195
196 /**
197 * Called if the task was canceled before it was completed. Gives the class a chance
Jeff Brownb19a71a2012-01-31 11:48:39 -0800198 * 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}.
Dmitri Plotnikovbef9c7a2010-06-16 15:38:07 -0700202 */
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800203 public void onCanceled(D data) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800204 }
Dmitri Plotnikov4afde4f2011-01-18 09:41:29 -0800205
Dianne Hackborn247fe742011-01-08 17:25:57 -0800206 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.
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800216 if (DEBUG) Slog.v(TAG, "Waiting until "
217 + (mLastLoadCompleteTime+mUpdateThrottle)
218 + " to execute: " + mTask);
Dianne Hackborn247fe742011-01-08 17:25:57 -0800219 mTask.waiting = true;
220 mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
221 return;
222 }
223 }
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800224 if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -0800225 mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
Dianne Hackborn247fe742011-01-08 17:25:57 -0800226 }
227 }
228
229 void dispatchOnCancelled(LoadTask task, D data) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800230 onCanceled(data);
Dianne Hackborn247fe742011-01-08 17:25:57 -0800231 if (mCancellingTask == task) {
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800232 if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
Dianne Hackborn247fe742011-01-08 17:25:57 -0800233 mLastLoadCompleteTime = SystemClock.uptimeMillis();
234 mCancellingTask = null;
Jeff Brownb19a71a2012-01-31 11:48:39 -0800235 if (DEBUG) Slog.v(TAG, "Delivering cancellation");
236 deliverCancellation();
Dianne Hackborn247fe742011-01-08 17:25:57 -0800237 executePendingTask();
238 }
239 }
240
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800241 void dispatchOnLoadComplete(LoadTask task, D data) {
242 if (mTask != task) {
Dianne Hackborn540f86a2011-01-11 17:52:22 -0800243 if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
Dianne Hackborn247fe742011-01-08 17:25:57 -0800244 dispatchOnCancelled(task, data);
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800245 } else {
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800246 if (isAbandoned()) {
247 // This cursor has been abandoned; just cancel the new data.
248 onCanceled(data);
249 } else {
250 mLastLoadCompleteTime = SystemClock.uptimeMillis();
251 mTask = null;
252 if (DEBUG) Slog.v(TAG, "Delivering result");
253 deliverResult(data);
254 }
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800255 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500256 }
257
258 /**
Jeff Brownb19a71a2012-01-31 11:48:39 -0800259 * Called on a worker thread to perform the actual load and to return
260 * the result of the load operation.
261 *
262 * Implementations should not deliver the result directly, but should return them
263 * from this method, which will eventually end up calling {@link #deliverResult} on
264 * the UI thread. If implementations need to process the results on the UI thread
265 * they may override {@link #deliverResult} and do so there.
266 *
267 * To support cancellation, this method should periodically check the value of
268 * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
269 * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
270 * directly instead of polling {@link #isLoadInBackgroundCanceled}.
271 *
272 * When the load is canceled, this method may either return normally or throw
273 * {@link OperationCanceledException}. In either case, the {@link Loader} will
274 * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
275 * result object, if any.
276 *
277 * @return The result of the load operation.
278 *
279 * @throws OperationCanceledException if the load is canceled during execution.
280 *
281 * @see #isLoadInBackgroundCanceled
282 * @see #cancelLoadInBackground
283 * @see #onCanceled
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500284 */
285 public abstract D loadInBackground();
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -0800286
287 /**
Jeff Brownb19a71a2012-01-31 11:48:39 -0800288 * Calls {@link #loadInBackground()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800289 *
Jeff Brownb19a71a2012-01-31 11:48:39 -0800290 * This method is reserved for use by the loader framework.
291 * Subclasses should override {@link #loadInBackground} instead of this method.
292 *
293 * @return The result of the load operation.
294 *
295 * @throws OperationCanceledException if the load is canceled during execution.
296 *
297 * @see #loadInBackground
Dianne Hackborn247fe742011-01-08 17:25:57 -0800298 */
299 protected D onLoadInBackground() {
300 return loadInBackground();
301 }
302
303 /**
Jeff Brownb19a71a2012-01-31 11:48:39 -0800304 * Called on the main thread to abort a load in progress.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800305 *
Jeff Brownb19a71a2012-01-31 11:48:39 -0800306 * Override this method to abort the current invocation of {@link #loadInBackground}
307 * that is running in the background on a worker thread.
308 *
309 * This method should do nothing if {@link #loadInBackground} has not started
310 * running or if it has already finished.
311 *
312 * @see #loadInBackground
Jeff Brown75ea64f2012-01-25 19:37:13 -0800313 */
Jeff Brownb19a71a2012-01-31 11:48:39 -0800314 public void cancelLoadInBackground() {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800315 }
316
317 /**
Jeff Brownb19a71a2012-01-31 11:48:39 -0800318 * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800319 *
Jeff Brownb19a71a2012-01-31 11:48:39 -0800320 * @return True if the current invocation of {@link #loadInBackground} is being canceled.
321 *
322 * @see #loadInBackground
Jeff Brown75ea64f2012-01-25 19:37:13 -0800323 */
Jeff Brownb19a71a2012-01-31 11:48:39 -0800324 public boolean isLoadInBackgroundCanceled() {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800325 return mCancellingTask != null;
326 }
327
328 /**
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -0800329 * Locks the current thread until the loader completes the current load
330 * operation. Returns immediately if there is no load operation running.
Dmitri Plotnikov59d8edd2011-01-09 11:05:50 -0800331 * Should not be called from the UI thread: calling it from the UI
332 * thread would cause a deadlock.
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -0800333 * <p>
Dianne Hackborn247fe742011-01-08 17:25:57 -0800334 * Use for testing only. <b>Never</b> call this from a UI thread.
Dmitri Plotnikov4afde4f2011-01-18 09:41:29 -0800335 *
336 * @hide
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -0800337 */
338 public void waitForLoader() {
339 LoadTask task = mTask;
340 if (task != null) {
Jeff Brownb19a71a2012-01-31 11:48:39 -0800341 task.waitForLoader();
Dmitri Plotnikovcd3676e2011-01-06 18:39:33 -0800342 }
343 }
Dianne Hackborn247fe742011-01-08 17:25:57 -0800344
345 @Override
346 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
347 super.dump(prefix, fd, writer, args);
348 if (mTask != null) {
349 writer.print(prefix); writer.print("mTask="); writer.print(mTask);
350 writer.print(" waiting="); writer.println(mTask.waiting);
351 }
352 if (mCancellingTask != null) {
353 writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
354 writer.print(" waiting="); writer.println(mCancellingTask.waiting);
355 }
356 if (mUpdateThrottle != 0) {
357 writer.print(prefix); writer.print("mUpdateThrottle=");
358 TimeUtils.formatDuration(mUpdateThrottle, writer);
359 writer.print(" mLastLoadCompleteTime=");
360 TimeUtils.formatDuration(mLastLoadCompleteTime,
361 SystemClock.uptimeMillis(), writer);
362 writer.println();
363 }
364 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500365}