blob: 2d2a90d40ecf2144bff53a761e0a83afbb5143f2 [file] [log] [blame]
Jeff Hamilton9911b7f2010-05-15 02:20:31 -05001/*
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.database.ContentObserver;
20import android.os.Handler;
Dianne Hackborna2ea7472010-12-20 12:10:01 -080021import android.util.DebugUtils;
22
23import java.io.FileDescriptor;
24import java.io.PrintWriter;
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050025
26/**
27 * An abstract class that performs asynchronous loading of data. While Loaders are active
28 * they should monitor the source of their data and deliver new results when the contents
Dianne Hackborn9567a662011-04-19 18:44:03 -070029 * change. See {@link android.app.LoaderManager} for more detail.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050030 *
Dianne Hackborn247fe742011-01-08 17:25:57 -080031 * <p><b>Note on threading:</b> Clients of loaders should as a rule perform
32 * any calls on to a Loader from the main thread of their process (that is,
33 * the thread the Activity callbacks and other things occur on). Subclasses
34 * of Loader (such as {@link AsyncTaskLoader}) will often perform their work
35 * in a separate thread, but when delivering their results this too should
36 * be done on the main thread.</p>
37 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -080038 * <p>Subclasses generally must implement at least {@link #onStartLoading()},
Dianne Hackborn9567a662011-04-19 18:44:03 -070039 * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.</p>
40 *
41 * <p>Most implementations should not derive directly from this class, but
42 * instead inherit from {@link AsyncTaskLoader}.</p>
Joe Fernandezb54e7a32011-10-03 15:09:50 -070043 *
44 * <div class="special reference">
45 * <h3>Developer Guides</h3>
46 * <p>For more information about using loaders, read the
47 * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
48 * </div>
Dianne Hackborna2ea7472010-12-20 12:10:01 -080049 *
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050050 * @param <D> The result returned when the load is complete
51 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -080052public class Loader<D> {
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050053 int mId;
54 OnLoadCompleteListener<D> mListener;
55 Context mContext;
Dianne Hackborna2ea7472010-12-20 12:10:01 -080056 boolean mStarted = false;
Dianne Hackborn260c3c72011-01-30 16:55:55 -080057 boolean mAbandoned = false;
Dianne Hackborna2ea7472010-12-20 12:10:01 -080058 boolean mReset = true;
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -080059 boolean mContentChanged = false;
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050060
61 public final class ForceLoadContentObserver extends ContentObserver {
62 public ForceLoadContentObserver() {
63 super(new Handler());
64 }
65
66 @Override
67 public boolean deliverSelfNotifications() {
68 return true;
69 }
70
71 @Override
72 public void onChange(boolean selfChange) {
Makoto Onukiec37c882010-08-02 16:57:01 -070073 onContentChanged();
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050074 }
75 }
76
77 public interface OnLoadCompleteListener<D> {
78 /**
79 * Called on the thread that created the Loader when the load is complete.
80 *
81 * @param loader the loader that completed the load
82 * @param data the result of the load
83 */
84 public void onLoadComplete(Loader<D> loader, D data);
85 }
86
87 /**
Dianne Hackborn9567a662011-04-19 18:44:03 -070088 * Stores away the application context associated with context.
89 * Since Loaders can be used across multiple activities it's dangerous to
90 * store the context directly; always use {@link #getContext()} to retrieve
91 * the Loader's Context, don't use the constructor argument directly.
92 * The Context returned by {@link #getContext} is safe to use across
93 * Activity instances.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050094 *
95 * @param context used to retrieve the application context.
96 */
97 public Loader(Context context) {
98 mContext = context.getApplicationContext();
99 }
100
101 /**
102 * Sends the result of the load to the registered listener. Should only be called by subclasses.
103 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800104 * Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500105 *
106 * @param data the result of the load
107 */
108 public void deliverResult(D data) {
109 if (mListener != null) {
110 mListener.onLoadComplete(this, data);
111 }
112 }
113
114 /**
115 * @return an application context retrieved from the Context passed to the constructor.
116 */
117 public Context getContext() {
118 return mContext;
119 }
120
121 /**
122 * @return the ID of this loader
123 */
124 public int getId() {
125 return mId;
126 }
127
128 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800129 * Registers a class that will receive callbacks when a load is complete.
130 * The callback will be called on the process's main thread so it's safe to
131 * pass the results to widgets.
Makoto Onukiec37c882010-08-02 16:57:01 -0700132 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800133 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500134 */
135 public void registerListener(int id, OnLoadCompleteListener<D> listener) {
136 if (mListener != null) {
137 throw new IllegalStateException("There is already a listener registered");
138 }
139 mListener = listener;
140 mId = id;
141 }
142
143 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800144 * Remove a listener that was previously added with {@link #registerListener}.
145 *
146 * Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500147 */
148 public void unregisterListener(OnLoadCompleteListener<D> listener) {
149 if (mListener == null) {
150 throw new IllegalStateException("No listener register");
151 }
152 if (mListener != listener) {
153 throw new IllegalArgumentException("Attempting to unregister the wrong listener");
154 }
155 mListener = null;
156 }
157
158 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800159 * Return whether this load has been started. That is, its {@link #startLoading()}
160 * has been called and no calls to {@link #stopLoading()} or
161 * {@link #reset()} have yet been made.
162 */
163 public boolean isStarted() {
164 return mStarted;
165 }
166
167 /**
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800168 * Return whether this loader has been abandoned. In this state, the
169 * loader <em>must not</em> report any new data, and <em>must</em> keep
170 * its last reported data valid until it is finally reset.
171 */
172 public boolean isAbandoned() {
173 return mAbandoned;
174 }
175
176 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800177 * Return whether this load has been reset. That is, either the loader
178 * has not yet been started for the first time, or its {@link #reset()}
179 * has been called.
180 */
181 public boolean isReset() {
182 return mReset;
183 }
184
185 /**
Dianne Hackborn2c84cfc2011-10-31 15:39:59 -0700186 * This function will normally be called for you automatically by
187 * {@link android.app.LoaderManager} when the associated fragment/activity
188 * is being started. When using a Loader with {@link android.app.LoaderManager},
189 * you <em>must not</em> call this method yourself, or you will conflict
190 * with its management of the Loader.
191 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800192 * Starts an asynchronous load of the Loader's data. When the result
193 * is ready the callbacks will be called on the process's main thread.
194 * If a previous load has been completed and is still valid
195 * the result may be passed to the callbacks immediately.
196 * The loader will monitor the source of
197 * the data set and may deliver future callbacks if the source changes.
198 * Calling {@link #stopLoading} will stop the delivery of callbacks.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500199 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800200 * <p>This updates the Loader's internal state so that
201 * {@link #isStarted()} and {@link #isReset()} will return the correct
202 * values, and then calls the implementation's {@link #onStartLoading()}.
203 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800204 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500205 */
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800206 public final void startLoading() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800207 mStarted = true;
208 mReset = false;
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800209 mAbandoned = false;
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800210 onStartLoading();
211 }
212
213 /**
214 * Subclasses must implement this to take care of loading their data,
215 * as per {@link #startLoading()}. This is not called by clients directly,
216 * but as a result of a call to {@link #startLoading()}.
217 */
218 protected void onStartLoading() {
219 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500220
221 /**
222 * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800223 * loaded data set and load a new one. This simply calls through to the
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800224 * implementation's {@link #onForceLoad()}. You generally should only call this
225 * when the loader is started -- that is, {@link #isStarted()} returns true.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800226 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800227 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500228 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800229 public void forceLoad() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800230 onForceLoad();
231 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500232
233 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800234 * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800235 * This will always be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500236 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800237 protected void onForceLoad() {
238 }
239
240 /**
Dianne Hackborn2c84cfc2011-10-31 15:39:59 -0700241 * This function will normally be called for you automatically by
242 * {@link android.app.LoaderManager} when the associated fragment/activity
243 * is being stopped. When using a Loader with {@link android.app.LoaderManager},
244 * you <em>must not</em> call this method yourself, or you will conflict
245 * with its management of the Loader.
246 *
247 * <p>Stops delivery of updates until the next time {@link #startLoading()} is called.
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800248 * Implementations should <em>not</em> invalidate their data at this point --
249 * clients are still free to use the last data the loader reported. They will,
250 * however, typically stop reporting new data if the data changes; they can
251 * still monitor for changes, but must not report them to the client until and
252 * if {@link #startLoading()} is later called.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800253 *
254 * <p>This updates the Loader's internal state so that
255 * {@link #isStarted()} will return the correct
256 * value, and then calls the implementation's {@link #onStopLoading()}.
257 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800258 * <p>Must be called from the process's main thread.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800259 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800260 public void stopLoading() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800261 mStarted = false;
262 onStopLoading();
263 }
264
265 /**
266 * Subclasses must implement this to take care of stopping their loader,
267 * as per {@link #stopLoading()}. This is not called by clients directly,
268 * but as a result of a call to {@link #stopLoading()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800269 * This will always be called from the process's main thread.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800270 */
271 protected void onStopLoading() {
272 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500273
274 /**
Dianne Hackborn2c84cfc2011-10-31 15:39:59 -0700275 * This function will normally be called for you automatically by
276 * {@link android.app.LoaderManager} when restarting a Loader. When using
277 * a Loader with {@link android.app.LoaderManager},
278 * you <em>must not</em> call this method yourself, or you will conflict
279 * with its management of the Loader.
280 *
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800281 * Tell the Loader that it is being abandoned. This is called prior
282 * to {@link #reset} to have it retain its current data but not report
283 * any new data.
284 */
285 public void abandon() {
286 mAbandoned = true;
287 onAbandon();
288 }
289
290 /**
291 * Subclasses implement this to take care of being abandoned. This is
292 * an optional intermediate state prior to {@link #onReset()} -- it means that
293 * the client is no longer interested in any new data from the loader,
294 * so the loader must not report any further updates. However, the
295 * loader <em>must</em> keep its last reported data valid until the final
296 * {@link #onReset()} happens. You can retrieve the current abandoned
297 * state with {@link #isAbandoned}.
298 */
299 protected void onAbandon() {
300 }
301
302 /**
Dianne Hackborn2c84cfc2011-10-31 15:39:59 -0700303 * This function will normally be called for you automatically by
304 * {@link android.app.LoaderManager} when destroying a Loader. When using
305 * a Loader with {@link android.app.LoaderManager},
306 * you <em>must not</em> call this method yourself, or you will conflict
307 * with its management of the Loader.
308 *
Dianne Hackbornc9189352010-12-15 14:57:25 -0800309 * Resets the state of the Loader. The Loader should at this point free
310 * all of its resources, since it may never be called again; however, its
311 * {@link #startLoading()} may later be called at which point it must be
312 * able to start running again.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500313 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800314 * <p>This updates the Loader's internal state so that
315 * {@link #isStarted()} and {@link #isReset()} will return the correct
316 * values, and then calls the implementation's {@link #onReset()}.
317 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800318 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500319 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800320 public void reset() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800321 onReset();
322 mReset = true;
323 mStarted = false;
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800324 mAbandoned = false;
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800325 mContentChanged = false;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800326 }
327
328 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800329 * Subclasses must implement this to take care of resetting their loader,
330 * as per {@link #reset()}. This is not called by clients directly,
331 * but as a result of a call to {@link #reset()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800332 * This will always be called from the process's main thread.
Dianne Hackbornc9189352010-12-15 14:57:25 -0800333 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800334 protected void onReset() {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800335 }
Makoto Onukiec37c882010-08-02 16:57:01 -0700336
337 /**
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800338 * Take the current flag indicating whether the loader's content had
339 * changed while it was stopped. If it had, true is returned and the
340 * flag is cleared.
341 */
342 public boolean takeContentChanged() {
343 boolean res = mContentChanged;
344 mContentChanged = false;
345 return res;
346 }
347
348 /**
349 * Called when {@link ForceLoadContentObserver} detects a change. The
350 * default implementation checks to see if the loader is currently started;
351 * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
352 * so that {@link #takeContentChanged()} returns true.
Makoto Onukiec37c882010-08-02 16:57:01 -0700353 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800354 * <p>Must be called from the process's main thread.
Makoto Onukiec37c882010-08-02 16:57:01 -0700355 */
356 public void onContentChanged() {
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800357 if (mStarted) {
358 forceLoad();
359 } else {
360 // This loader has been stopped, so we don't want to load
361 // new data right now... but keep track of it changing to
362 // refresh later if we start again.
363 mContentChanged = true;
364 }
Makoto Onukiec37c882010-08-02 16:57:01 -0700365 }
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800366
367 /**
368 * For debugging, converts an instance of the Loader's data class to
369 * a string that can be printed. Must handle a null data.
370 */
371 public String dataToString(D data) {
372 StringBuilder sb = new StringBuilder(64);
373 DebugUtils.buildShortClassTag(data, sb);
374 sb.append("}");
375 return sb.toString();
376 }
377
378 @Override
379 public String toString() {
380 StringBuilder sb = new StringBuilder(64);
381 DebugUtils.buildShortClassTag(this, sb);
382 sb.append(" id=");
383 sb.append(mId);
384 sb.append("}");
385 return sb.toString();
386 }
387
388 /**
389 * Print the Loader's state into the given stream.
390 *
391 * @param prefix Text to print at the front of each line.
392 * @param fd The raw file descriptor that the dump is being sent to.
393 * @param writer A PrintWriter to which the dump is to be set.
394 * @param args Additional arguments to the dump request.
395 */
396 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
397 writer.print(prefix); writer.print("mId="); writer.print(mId);
398 writer.print(" mListener="); writer.println(mListener);
399 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800400 writer.print(" mContentChanged="); writer.print(mContentChanged);
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800401 writer.print(" mAbandoned="); writer.print(mAbandoned);
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800402 writer.print(" mReset="); writer.println(mReset);
403 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500404}