blob: a9d611768b460da50dc6fc730bb77a88e9025ea0 [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
Makoto Onukiec37c882010-08-02 16:57:01 -070029 * change.
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()},
39 * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.
40 *
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050041 * @param <D> The result returned when the load is complete
42 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -080043public class Loader<D> {
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050044 int mId;
45 OnLoadCompleteListener<D> mListener;
46 Context mContext;
Dianne Hackborna2ea7472010-12-20 12:10:01 -080047 boolean mStarted = false;
Dianne Hackborn260c3c72011-01-30 16:55:55 -080048 boolean mAbandoned = false;
Dianne Hackborna2ea7472010-12-20 12:10:01 -080049 boolean mReset = true;
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -080050 boolean mContentChanged = false;
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050051
52 public final class ForceLoadContentObserver extends ContentObserver {
53 public ForceLoadContentObserver() {
54 super(new Handler());
55 }
56
57 @Override
58 public boolean deliverSelfNotifications() {
59 return true;
60 }
61
62 @Override
63 public void onChange(boolean selfChange) {
Makoto Onukiec37c882010-08-02 16:57:01 -070064 onContentChanged();
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050065 }
66 }
67
68 public interface OnLoadCompleteListener<D> {
69 /**
70 * Called on the thread that created the Loader when the load is complete.
71 *
72 * @param loader the loader that completed the load
73 * @param data the result of the load
74 */
75 public void onLoadComplete(Loader<D> loader, D data);
76 }
77
78 /**
79 * Stores away the application context associated with context. Since Loaders can be used
80 * across multiple activities it's dangerous to store the context directly.
81 *
82 * @param context used to retrieve the application context.
83 */
84 public Loader(Context context) {
85 mContext = context.getApplicationContext();
86 }
87
88 /**
89 * Sends the result of the load to the registered listener. Should only be called by subclasses.
90 *
Dianne Hackborn247fe742011-01-08 17:25:57 -080091 * Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050092 *
93 * @param data the result of the load
94 */
95 public void deliverResult(D data) {
96 if (mListener != null) {
97 mListener.onLoadComplete(this, data);
98 }
99 }
100
101 /**
102 * @return an application context retrieved from the Context passed to the constructor.
103 */
104 public Context getContext() {
105 return mContext;
106 }
107
108 /**
109 * @return the ID of this loader
110 */
111 public int getId() {
112 return mId;
113 }
114
115 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800116 * Registers a class that will receive callbacks when a load is complete.
117 * The callback will be called on the process's main thread so it's safe to
118 * pass the results to widgets.
Makoto Onukiec37c882010-08-02 16:57:01 -0700119 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800120 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500121 */
122 public void registerListener(int id, OnLoadCompleteListener<D> listener) {
123 if (mListener != null) {
124 throw new IllegalStateException("There is already a listener registered");
125 }
126 mListener = listener;
127 mId = id;
128 }
129
130 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800131 * Remove a listener that was previously added with {@link #registerListener}.
132 *
133 * Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500134 */
135 public void unregisterListener(OnLoadCompleteListener<D> listener) {
136 if (mListener == null) {
137 throw new IllegalStateException("No listener register");
138 }
139 if (mListener != listener) {
140 throw new IllegalArgumentException("Attempting to unregister the wrong listener");
141 }
142 mListener = null;
143 }
144
145 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800146 * Return whether this load has been started. That is, its {@link #startLoading()}
147 * has been called and no calls to {@link #stopLoading()} or
148 * {@link #reset()} have yet been made.
149 */
150 public boolean isStarted() {
151 return mStarted;
152 }
153
154 /**
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800155 * Return whether this loader has been abandoned. In this state, the
156 * loader <em>must not</em> report any new data, and <em>must</em> keep
157 * its last reported data valid until it is finally reset.
158 */
159 public boolean isAbandoned() {
160 return mAbandoned;
161 }
162
163 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800164 * Return whether this load has been reset. That is, either the loader
165 * has not yet been started for the first time, or its {@link #reset()}
166 * has been called.
167 */
168 public boolean isReset() {
169 return mReset;
170 }
171
172 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800173 * Starts an asynchronous load of the Loader's data. When the result
174 * is ready the callbacks will be called on the process's main thread.
175 * If a previous load has been completed and is still valid
176 * the result may be passed to the callbacks immediately.
177 * The loader will monitor the source of
178 * the data set and may deliver future callbacks if the source changes.
179 * Calling {@link #stopLoading} will stop the delivery of callbacks.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500180 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800181 * <p>This updates the Loader's internal state so that
182 * {@link #isStarted()} and {@link #isReset()} will return the correct
183 * values, and then calls the implementation's {@link #onStartLoading()}.
184 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800185 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500186 */
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800187 public final void startLoading() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800188 mStarted = true;
189 mReset = false;
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800190 mAbandoned = false;
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800191 onStartLoading();
192 }
193
194 /**
195 * Subclasses must implement this to take care of loading their data,
196 * as per {@link #startLoading()}. This is not called by clients directly,
197 * but as a result of a call to {@link #startLoading()}.
198 */
199 protected void onStartLoading() {
200 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500201
202 /**
203 * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800204 * loaded data set and load a new one. This simply calls through to the
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800205 * implementation's {@link #onForceLoad()}. You generally should only call this
206 * when the loader is started -- that is, {@link #isStarted()} returns true.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800207 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800208 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500209 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800210 public void forceLoad() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800211 onForceLoad();
212 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500213
214 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800215 * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800216 * This will always be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500217 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800218 protected void onForceLoad() {
219 }
220
221 /**
222 * Stops delivery of updates until the next time {@link #startLoading()} is called.
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800223 * Implementations should <em>not</em> invalidate their data at this point --
224 * clients are still free to use the last data the loader reported. They will,
225 * however, typically stop reporting new data if the data changes; they can
226 * still monitor for changes, but must not report them to the client until and
227 * if {@link #startLoading()} is later called.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800228 *
229 * <p>This updates the Loader's internal state so that
230 * {@link #isStarted()} will return the correct
231 * value, and then calls the implementation's {@link #onStopLoading()}.
232 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800233 * <p>Must be called from the process's main thread.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800234 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800235 public void stopLoading() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800236 mStarted = false;
237 onStopLoading();
238 }
239
240 /**
241 * Subclasses must implement this to take care of stopping their loader,
242 * as per {@link #stopLoading()}. This is not called by clients directly,
243 * but as a result of a call to {@link #stopLoading()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800244 * This will always be called from the process's main thread.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800245 */
246 protected void onStopLoading() {
247 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500248
249 /**
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800250 * Tell the Loader that it is being abandoned. This is called prior
251 * to {@link #reset} to have it retain its current data but not report
252 * any new data.
253 */
254 public void abandon() {
255 mAbandoned = true;
256 onAbandon();
257 }
258
259 /**
260 * Subclasses implement this to take care of being abandoned. This is
261 * an optional intermediate state prior to {@link #onReset()} -- it means that
262 * the client is no longer interested in any new data from the loader,
263 * so the loader must not report any further updates. However, the
264 * loader <em>must</em> keep its last reported data valid until the final
265 * {@link #onReset()} happens. You can retrieve the current abandoned
266 * state with {@link #isAbandoned}.
267 */
268 protected void onAbandon() {
269 }
270
271 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800272 * Resets the state of the Loader. The Loader should at this point free
273 * all of its resources, since it may never be called again; however, its
274 * {@link #startLoading()} may later be called at which point it must be
275 * able to start running again.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500276 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800277 * <p>This updates the Loader's internal state so that
278 * {@link #isStarted()} and {@link #isReset()} will return the correct
279 * values, and then calls the implementation's {@link #onReset()}.
280 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800281 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500282 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800283 public void reset() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800284 onReset();
285 mReset = true;
286 mStarted = false;
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800287 mAbandoned = false;
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800288 mContentChanged = false;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800289 }
290
291 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800292 * Subclasses must implement this to take care of resetting their loader,
293 * as per {@link #reset()}. This is not called by clients directly,
294 * but as a result of a call to {@link #reset()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800295 * This will always be called from the process's main thread.
Dianne Hackbornc9189352010-12-15 14:57:25 -0800296 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800297 protected void onReset() {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800298 }
Makoto Onukiec37c882010-08-02 16:57:01 -0700299
300 /**
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800301 * Take the current flag indicating whether the loader's content had
302 * changed while it was stopped. If it had, true is returned and the
303 * flag is cleared.
304 */
305 public boolean takeContentChanged() {
306 boolean res = mContentChanged;
307 mContentChanged = false;
308 return res;
309 }
310
311 /**
312 * Called when {@link ForceLoadContentObserver} detects a change. The
313 * default implementation checks to see if the loader is currently started;
314 * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
315 * so that {@link #takeContentChanged()} returns true.
Makoto Onukiec37c882010-08-02 16:57:01 -0700316 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800317 * <p>Must be called from the process's main thread.
Makoto Onukiec37c882010-08-02 16:57:01 -0700318 */
319 public void onContentChanged() {
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800320 if (mStarted) {
321 forceLoad();
322 } else {
323 // This loader has been stopped, so we don't want to load
324 // new data right now... but keep track of it changing to
325 // refresh later if we start again.
326 mContentChanged = true;
327 }
Makoto Onukiec37c882010-08-02 16:57:01 -0700328 }
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800329
330 /**
331 * For debugging, converts an instance of the Loader's data class to
332 * a string that can be printed. Must handle a null data.
333 */
334 public String dataToString(D data) {
335 StringBuilder sb = new StringBuilder(64);
336 DebugUtils.buildShortClassTag(data, sb);
337 sb.append("}");
338 return sb.toString();
339 }
340
341 @Override
342 public String toString() {
343 StringBuilder sb = new StringBuilder(64);
344 DebugUtils.buildShortClassTag(this, sb);
345 sb.append(" id=");
346 sb.append(mId);
347 sb.append("}");
348 return sb.toString();
349 }
350
351 /**
352 * Print the Loader's state into the given stream.
353 *
354 * @param prefix Text to print at the front of each line.
355 * @param fd The raw file descriptor that the dump is being sent to.
356 * @param writer A PrintWriter to which the dump is to be set.
357 * @param args Additional arguments to the dump request.
358 */
359 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
360 writer.print(prefix); writer.print("mId="); writer.print(mId);
361 writer.print(" mListener="); writer.println(mListener);
362 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800363 writer.print(" mContentChanged="); writer.print(mContentChanged);
Dianne Hackborn260c3c72011-01-30 16:55:55 -0800364 writer.print(" mAbandoned="); writer.print(mAbandoned);
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800365 writer.print(" mReset="); writer.println(mReset);
366 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500367}