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.util.TimeUtils;
23import android.util.Log;
24
25import java.io.FileDescriptor;
26import java.io.PrintWriter;
27import java.util.concurrent.CountDownLatch;
28
29/**
30 * Static library support version of the framework's {@link android.content.AsyncTaskLoader}.
31 * Used to write apps that run on platforms prior to Android 3.0.  When running
32 * on Android 3.0 or above, this implementation is still used; it does not try
33 * to switch to the framework's implementation.  See the framework SDK
34 * documentation for a class overview.
35 */
36public abstract class AsyncTaskLoader<D> extends Loader<D> {
37    static final String TAG = "AsyncTaskLoader";
38    static final boolean DEBUG = false;
39
40    final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable {
41
42        D result;
43        boolean waiting;
44
45        private CountDownLatch done = new CountDownLatch(1);
46
47        /* Runs on a worker thread */
48        @Override
49        protected D doInBackground(Void... params) {
50            if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
51            result = AsyncTaskLoader.this.onLoadInBackground();
52            if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
53            return result;
54        }
55
56        /* Runs on the UI thread */
57        @Override
58        protected void onPostExecute(D data) {
59            if (DEBUG) Log.v(TAG, this + " onPostExecute");
60            try {
61                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
62            } finally {
63                done.countDown();
64            }
65        }
66
67        @Override
68        protected void onCancelled() {
69            if (DEBUG) Log.v(TAG, this + " onCancelled");
70            try {
71                AsyncTaskLoader.this.dispatchOnCancelled(this, result);
72            } finally {
73                done.countDown();
74            }
75        }
76
77        @Override
78        public void run() {
79            waiting = false;
80            AsyncTaskLoader.this.executePendingTask();
81        }
82    }
83
84    volatile LoadTask mTask;
85    volatile LoadTask mCancellingTask;
86
87    long mUpdateThrottle;
88    long mLastLoadCompleteTime = -10000;
89    Handler mHandler;
90
91    public AsyncTaskLoader(Context context) {
92        super(context);
93    }
94
95    /**
96     * Set amount to throttle updates by.  This is the minimum time from
97     * when the last {@link #onLoadInBackground()} call has completed until
98     * a new load is scheduled.
99     *
100     * @param delayMS Amount of delay, in milliseconds.
101     */
102    public void setUpdateThrottle(long delayMS) {
103        mUpdateThrottle = delayMS;
104        if (delayMS != 0) {
105            mHandler = new Handler();
106        }
107    }
108
109    @Override
110    protected void onForceLoad() {
111        super.onForceLoad();
112        cancelLoad();
113        mTask = new LoadTask();
114        if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
115        executePendingTask();
116    }
117
118    /**
119     * Attempt to cancel the current load task. See {@link android.os.AsyncTask#cancel(boolean)}
120     * for more info.  Must be called on the main thread of the process.
121     *
122     * <p>Cancelling is not an immediate operation, since the load is performed
123     * in a background thread.  If there is currently a load in progress, this
124     * method requests that the load be cancelled, and notes this is the case;
125     * once the background thread has completed its work its remaining state
126     * will be cleared.  If another load request comes in during this time,
127     * it will be held until the cancelled load is complete.
128     *
129     * @return Returns <tt>false</tt> if the task could not be cancelled,
130     *         typically because it has already completed normally, or
131     *         because {@link #startLoading()} hasn't been called; returns
132     *         <tt>true</tt> otherwise.
133     */
134    public boolean cancelLoad() {
135        if (DEBUG) Log.v(TAG, "cancelLoad: mTask=" + mTask);
136        if (mTask != null) {
137            if (mCancellingTask != null) {
138                // There was a pending task already waiting for a previous
139                // one being canceled; just drop it.
140                if (DEBUG) Log.v(TAG,
141                        "cancelLoad: still waiting for cancelled task; dropping next");
142                if (mTask.waiting) {
143                    mTask.waiting = false;
144                    mHandler.removeCallbacks(mTask);
145                }
146                mTask = null;
147                return false;
148            } else if (mTask.waiting) {
149                // There is a task, but it is waiting for the time it should
150                // execute.  We can just toss it.
151                if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
152                mTask.waiting = false;
153                mHandler.removeCallbacks(mTask);
154                mTask = null;
155                return false;
156            } else {
157                boolean cancelled = mTask.cancel(false);
158                if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
159                if (cancelled) {
160                    mCancellingTask = mTask;
161                }
162                mTask = null;
163                return cancelled;
164            }
165        }
166        return false;
167    }
168
169    /**
170     * Called if the task was canceled before it was completed.  Gives the class a chance
171     * to properly dispose of the result.
172     */
173    public void onCanceled(D data) {
174    }
175
176    void executePendingTask() {
177        if (mCancellingTask == null && mTask != null) {
178            if (mTask.waiting) {
179                mTask.waiting = false;
180                mHandler.removeCallbacks(mTask);
181            }
182            if (mUpdateThrottle > 0) {
183                long now = SystemClock.uptimeMillis();
184                if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
185                    // Not yet time to do another load.
186                    if (DEBUG) Log.v(TAG, "Waiting until "
187                            + (mLastLoadCompleteTime+mUpdateThrottle)
188                            + " to execute: " + mTask);
189                    mTask.waiting = true;
190                    mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
191                    return;
192                }
193            }
194            if (DEBUG) Log.v(TAG, "Executing: " + mTask);
195            mTask.executeOnExecutor(ModernAsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
196        }
197    }
198
199    void dispatchOnCancelled(LoadTask task, D data) {
200        onCanceled(data);
201        if (mCancellingTask == task) {
202            if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
203            mLastLoadCompleteTime = SystemClock.uptimeMillis();
204            mCancellingTask = null;
205            executePendingTask();
206        }
207    }
208
209    void dispatchOnLoadComplete(LoadTask task, D data) {
210        if (mTask != task) {
211            if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
212            dispatchOnCancelled(task, data);
213        } else {
214            if (isAbandoned()) {
215                // This cursor has been abandoned; just cancel the new data.
216                onCanceled(data);
217            } else {
218                mLastLoadCompleteTime = SystemClock.uptimeMillis();
219                mTask = null;
220                if (DEBUG) Log.v(TAG, "Delivering result");
221                deliverResult(data);
222            }
223        }
224    }
225
226    /**
227     */
228    public abstract D loadInBackground();
229
230    /**
231     * Called on a worker thread to perform the actual load. Implementations should not deliver the
232     * result directly, but should return them from this method, which will eventually end up
233     * calling {@link #deliverResult} on the UI thread. If implementations need to process
234     * the results on the UI thread they may override {@link #deliverResult} and do so
235     * there.
236     *
237     * @return Implementations must return the result of their load operation.
238     */
239    protected D onLoadInBackground() {
240        return loadInBackground();
241    }
242
243    /**
244     * Locks the current thread until the loader completes the current load
245     * operation. Returns immediately if there is no load operation running.
246     * Should not be called from the UI thread: calling it from the UI
247     * thread would cause a deadlock.
248     * <p>
249     * Use for testing only.  <b>Never</b> call this from a UI thread.
250     *
251     * @hide
252     */
253    public void waitForLoader() {
254        LoadTask task = mTask;
255        if (task != null) {
256            try {
257                task.done.await();
258            } catch (InterruptedException e) {
259                // Ignore
260            }
261        }
262    }
263
264    @Override
265    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
266        super.dump(prefix, fd, writer, args);
267        if (mTask != null) {
268            writer.print(prefix); writer.print("mTask="); writer.print(mTask);
269                    writer.print(" waiting="); writer.println(mTask.waiting);
270        }
271        if (mCancellingTask != null) {
272            writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
273                    writer.print(" waiting="); writer.println(mCancellingTask.waiting);
274        }
275        if (mUpdateThrottle != 0) {
276            writer.print(prefix); writer.print("mUpdateThrottle=");
277                    TimeUtils.formatDuration(mUpdateThrottle, writer);
278                    writer.print(" mLastLoadCompleteTime=");
279                    TimeUtils.formatDuration(mLastLoadCompleteTime,
280                            SystemClock.uptimeMillis(), writer);
281                    writer.println();
282        }
283    }
284}
285