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