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