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