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 *
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
57public 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
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
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
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
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. */
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
140    public AsyncTaskLoader(Context context) {
141        this(context, AsyncTask.THREAD_POOL_EXECUTOR);
142    }
143
144    /** {@hide} */
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     */
157    public void setUpdateThrottle(long delayMS) {
158        mUpdateThrottle = delayMS;
159        if (delayMS != 0) {
160            mHandler = new Handler();
161        }
162    }
163
164    @Override
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
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     */
220    public void onCanceled(D data) {
221    }
222
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
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
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     */
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     */
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     */
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     */
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     */
357    public void waitForLoader() {
358        LoadTask task = mTask;
359        if (task != null) {
360            task.waitForLoader();
361        }
362    }
363
364    @Override
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