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.database.ContentObserver;
21import android.os.Handler;
22import android.support.v4.util.DebugUtils;
23
24import java.io.FileDescriptor;
25import java.io.PrintWriter;
26
27/**
28 * Static library support version of the framework's {@link android.content.Loader}.
29 * Used to write apps that run on platforms prior to Android 3.0.  When running
30 * on Android 3.0 or above, this implementation is still used; it does not try
31 * to switch to the framework's implementation.  See the framework SDK
32 * documentation for a class overview.
33 */
34public class Loader<D> {
35    int mId;
36    OnLoadCompleteListener<D> mListener;
37    Context mContext;
38    boolean mStarted = false;
39    boolean mAbandoned = false;
40    boolean mReset = true;
41    boolean mContentChanged = false;
42
43    /**
44     * An implementation of a ContentObserver that takes care of connecting
45     * it to the Loader to have the loader re-load its data when the observer
46     * is told it has changed.  You do not normally need to use this yourself;
47     * it is used for you by {@link android.support.v4.content.CursorLoader}
48     * to take care of executing an update when the cursor's backing data changes.
49     */
50    public final class ForceLoadContentObserver extends ContentObserver {
51        public ForceLoadContentObserver() {
52            super(new Handler());
53        }
54
55        @Override
56        public boolean deliverSelfNotifications() {
57            return true;
58        }
59
60        @Override
61        public void onChange(boolean selfChange) {
62            onContentChanged();
63        }
64    }
65
66    /**
67     * Interface that is implemented to discover when a Loader has finished
68     * loading its data.  You do not normally need to implement this yourself;
69     * it is used in the implementation of {@link android.support.v4.app.LoaderManager}
70     * to find out when a Loader it is managing has completed so that this can
71     * be reported to its client.  This interface should only be used if a
72     * Loader is not being used in conjunction with LoaderManager.
73     */
74    public interface OnLoadCompleteListener<D> {
75        /**
76         * Called on the thread that created the Loader when the load is complete.
77         *
78         * @param loader the loader that completed the load
79         * @param data the result of the load
80         */
81        public void onLoadComplete(Loader<D> loader, D data);
82    }
83
84    /**
85     * Stores away the application context associated with context. Since Loaders can be used
86     * across multiple activities it's dangerous to store the context directly.
87     *
88     * @param context used to retrieve the application context.
89     */
90    public Loader(Context context) {
91        mContext = context.getApplicationContext();
92    }
93
94    /**
95     * Sends the result of the load to the registered listener. Should only be called by subclasses.
96     *
97     * Must be called from the process's main thread.
98     *
99     * @param data the result of the load
100     */
101    public void deliverResult(D data) {
102        if (mListener != null) {
103            mListener.onLoadComplete(this, data);
104        }
105    }
106
107    /**
108     * @return an application context retrieved from the Context passed to the constructor.
109     */
110    public Context getContext() {
111        return mContext;
112    }
113
114    /**
115     * @return the ID of this loader
116     */
117    public int getId() {
118        return mId;
119    }
120
121    /**
122     * Registers a class that will receive callbacks when a load is complete.
123     * The callback will be called on the process's main thread so it's safe to
124     * pass the results to widgets.
125     *
126     * <p>Must be called from the process's main thread.
127     */
128    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
129        if (mListener != null) {
130            throw new IllegalStateException("There is already a listener registered");
131        }
132        mListener = listener;
133        mId = id;
134    }
135
136    /**
137     * Remove a listener that was previously added with {@link #registerListener}.
138     *
139     * Must be called from the process's main thread.
140     */
141    public void unregisterListener(OnLoadCompleteListener<D> listener) {
142        if (mListener == null) {
143            throw new IllegalStateException("No listener register");
144        }
145        if (mListener != listener) {
146            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
147        }
148        mListener = null;
149    }
150
151    /**
152     * Return whether this load has been started.  That is, its {@link #startLoading()}
153     * has been called and no calls to {@link #stopLoading()} or
154     * {@link #reset()} have yet been made.
155     */
156    public boolean isStarted() {
157        return mStarted;
158    }
159
160    /**
161     * Return whether this loader has been abandoned.  In this state, the
162     * loader <em>must not</em> report any new data, and <em>must</em> keep
163     * its last reported data valid until it is finally reset.
164     */
165    public boolean isAbandoned() {
166        return mAbandoned;
167    }
168
169    /**
170     * Return whether this load has been reset.  That is, either the loader
171     * has not yet been started for the first time, or its {@link #reset()}
172     * has been called.
173     */
174    public boolean isReset() {
175        return mReset;
176    }
177
178    /**
179     * Starts an asynchronous load of the Loader's data. When the result
180     * is ready the callbacks will be called on the process's main thread.
181     * If a previous load has been completed and is still valid
182     * the result may be passed to the callbacks immediately.
183     * The loader will monitor the source of
184     * the data set and may deliver future callbacks if the source changes.
185     * Calling {@link #stopLoading} will stop the delivery of callbacks.
186     *
187     * <p>This updates the Loader's internal state so that
188     * {@link #isStarted()} and {@link #isReset()} will return the correct
189     * values, and then calls the implementation's {@link #onStartLoading()}.
190     *
191     * <p>Must be called from the process's main thread.
192     */
193    public final void startLoading() {
194        mStarted = true;
195        mReset = false;
196        mAbandoned = false;
197        onStartLoading();
198    }
199
200    /**
201     * Subclasses must implement this to take care of loading their data,
202     * as per {@link #startLoading()}.  This is not called by clients directly,
203     * but as a result of a call to {@link #startLoading()}.
204     */
205    protected void onStartLoading() {
206    }
207
208    /**
209     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
210     * loaded data set and load a new one.  This simply calls through to the
211     * implementation's {@link #onForceLoad()}.  You generally should only call this
212     * when the loader is started -- that is, {@link #isStarted()} returns true.
213     *
214     * <p>Must be called from the process's main thread.
215     */
216    public void forceLoad() {
217        onForceLoad();
218    }
219
220    /**
221     * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
222     * This will always be called from the process's main thread.
223     */
224    protected void onForceLoad() {
225    }
226
227    /**
228     * Stops delivery of updates until the next time {@link #startLoading()} is called.
229     * Implementations should <em>not</em> invalidate their data at this point --
230     * clients are still free to use the last data the loader reported.  They will,
231     * however, typically stop reporting new data if the data changes; they can
232     * still monitor for changes, but must not report them to the client until and
233     * if {@link #startLoading()} is later called.
234     *
235     * <p>This updates the Loader's internal state so that
236     * {@link #isStarted()} will return the correct
237     * value, and then calls the implementation's {@link #onStopLoading()}.
238     *
239     * <p>Must be called from the process's main thread.
240     */
241    public void stopLoading() {
242        mStarted = false;
243        onStopLoading();
244    }
245
246    /**
247     * Subclasses must implement this to take care of stopping their loader,
248     * as per {@link #stopLoading()}.  This is not called by clients directly,
249     * but as a result of a call to {@link #stopLoading()}.
250     * This will always be called from the process's main thread.
251     */
252    protected void onStopLoading() {
253    }
254
255    /**
256     * Tell the Loader that it is being abandoned.  This is called prior
257     * to {@link #reset} to have it retain its current data but not report
258     * any new data.
259     */
260    public void abandon() {
261        mAbandoned = true;
262        onAbandon();
263    }
264
265    /**
266     * Subclasses implement this to take care of being abandoned.  This is
267     * an optional intermediate state prior to {@link #onReset()} -- it means that
268     * the client is no longer interested in any new data from the loader,
269     * so the loader must not report any further updates.  However, the
270     * loader <em>must</em> keep its last reported data valid until the final
271     * {@link #onReset()} happens.  You can retrieve the current abandoned
272     * state with {@link #isAbandoned}.
273     */
274    protected void onAbandon() {
275    }
276
277    /**
278     * Resets the state of the Loader.  The Loader should at this point free
279     * all of its resources, since it may never be called again; however, its
280     * {@link #startLoading()} may later be called at which point it must be
281     * able to start running again.
282     *
283     * <p>This updates the Loader's internal state so that
284     * {@link #isStarted()} and {@link #isReset()} will return the correct
285     * values, and then calls the implementation's {@link #onReset()}.
286     *
287     * <p>Must be called from the process's main thread.
288     */
289    public void reset() {
290        onReset();
291        mReset = true;
292        mStarted = false;
293        mAbandoned = false;
294        mContentChanged = false;
295    }
296
297    /**
298     * Subclasses must implement this to take care of resetting their loader,
299     * as per {@link #reset()}.  This is not called by clients directly,
300     * but as a result of a call to {@link #reset()}.
301     * This will always be called from the process's main thread.
302     */
303    protected void onReset() {
304    }
305
306    /**
307     * Take the current flag indicating whether the loader's content had
308     * changed while it was stopped.  If it had, true is returned and the
309     * flag is cleared.
310     */
311    public boolean takeContentChanged() {
312        boolean res = mContentChanged;
313        mContentChanged = false;
314        return res;
315    }
316
317    /**
318     * Called when {@link ForceLoadContentObserver} detects a change.  The
319     * default implementation checks to see if the loader is currently started;
320     * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
321     * so that {@link #takeContentChanged()} returns true.
322     *
323     * <p>Must be called from the process's main thread.
324     */
325    public void onContentChanged() {
326        if (mStarted) {
327            forceLoad();
328        } else {
329            // This loader has been stopped, so we don't want to load
330            // new data right now...  but keep track of it changing to
331            // refresh later if we start again.
332            mContentChanged = true;
333        }
334    }
335
336    /**
337     * For debugging, converts an instance of the Loader's data class to
338     * a string that can be printed.  Must handle a null data.
339     */
340    public String dataToString(D data) {
341        StringBuilder sb = new StringBuilder(64);
342        DebugUtils.buildShortClassTag(data, sb);
343        sb.append("}");
344        return sb.toString();
345    }
346
347    @Override
348    public String toString() {
349        StringBuilder sb = new StringBuilder(64);
350        DebugUtils.buildShortClassTag(this, sb);
351        sb.append(" id=");
352        sb.append(mId);
353        sb.append("}");
354        return sb.toString();
355    }
356
357    /**
358     * Print the Loader's state into the given stream.
359     *
360     * @param prefix Text to print at the front of each line.
361     * @param fd The raw file descriptor that the dump is being sent to.
362     * @param writer A PrintWriter to which the dump is to be set.
363     * @param args Additional arguments to the dump request.
364     */
365    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
366        writer.print(prefix); writer.print("mId="); writer.print(mId);
367                writer.print(" mListener="); writer.println(mListener);
368        writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
369                writer.print(" mContentChanged="); writer.print(mContentChanged);
370                writer.print(" mAbandoned="); writer.print(mAbandoned);
371                writer.print(" mReset="); writer.println(mReset);
372    }
373}