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