blob: d63fe6915b8c028a4895ba3fc221d28221e7de6f [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;
48 boolean mReset = true;
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -080049 boolean mContentChanged = false;
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050050
51 public final class ForceLoadContentObserver extends ContentObserver {
52 public ForceLoadContentObserver() {
53 super(new Handler());
54 }
55
56 @Override
57 public boolean deliverSelfNotifications() {
58 return true;
59 }
60
61 @Override
62 public void onChange(boolean selfChange) {
Makoto Onukiec37c882010-08-02 16:57:01 -070063 onContentChanged();
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050064 }
65 }
66
67 public interface OnLoadCompleteListener<D> {
68 /**
69 * Called on the thread that created the Loader when the load is complete.
70 *
71 * @param loader the loader that completed the load
72 * @param data the result of the load
73 */
74 public void onLoadComplete(Loader<D> loader, D data);
75 }
76
77 /**
78 * Stores away the application context associated with context. Since Loaders can be used
79 * across multiple activities it's dangerous to store the context directly.
80 *
81 * @param context used to retrieve the application context.
82 */
83 public Loader(Context context) {
84 mContext = context.getApplicationContext();
85 }
86
87 /**
88 * Sends the result of the load to the registered listener. Should only be called by subclasses.
89 *
Dianne Hackborn247fe742011-01-08 17:25:57 -080090 * Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -050091 *
92 * @param data the result of the load
93 */
94 public void deliverResult(D data) {
95 if (mListener != null) {
96 mListener.onLoadComplete(this, data);
97 }
98 }
99
100 /**
101 * @return an application context retrieved from the Context passed to the constructor.
102 */
103 public Context getContext() {
104 return mContext;
105 }
106
107 /**
108 * @return the ID of this loader
109 */
110 public int getId() {
111 return mId;
112 }
113
114 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800115 * Registers a class that will receive callbacks when a load is complete.
116 * The callback will be called on the process's main thread so it's safe to
117 * pass the results to widgets.
Makoto Onukiec37c882010-08-02 16:57:01 -0700118 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800119 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500120 */
121 public void registerListener(int id, OnLoadCompleteListener<D> listener) {
122 if (mListener != null) {
123 throw new IllegalStateException("There is already a listener registered");
124 }
125 mListener = listener;
126 mId = id;
127 }
128
129 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800130 * Remove a listener that was previously added with {@link #registerListener}.
131 *
132 * Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500133 */
134 public void unregisterListener(OnLoadCompleteListener<D> listener) {
135 if (mListener == null) {
136 throw new IllegalStateException("No listener register");
137 }
138 if (mListener != listener) {
139 throw new IllegalArgumentException("Attempting to unregister the wrong listener");
140 }
141 mListener = null;
142 }
143
144 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800145 * Return whether this load has been started. That is, its {@link #startLoading()}
146 * has been called and no calls to {@link #stopLoading()} or
147 * {@link #reset()} have yet been made.
148 */
149 public boolean isStarted() {
150 return mStarted;
151 }
152
153 /**
154 * Return whether this load has been reset. That is, either the loader
155 * has not yet been started for the first time, or its {@link #reset()}
156 * has been called.
157 */
158 public boolean isReset() {
159 return mReset;
160 }
161
162 /**
Dianne Hackborn247fe742011-01-08 17:25:57 -0800163 * Starts an asynchronous load of the Loader's data. When the result
164 * is ready the callbacks will be called on the process's main thread.
165 * If a previous load has been completed and is still valid
166 * the result may be passed to the callbacks immediately.
167 * The loader will monitor the source of
168 * the data set and may deliver future callbacks if the source changes.
169 * Calling {@link #stopLoading} will stop the delivery of callbacks.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500170 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800171 * <p>This updates the Loader's internal state so that
172 * {@link #isStarted()} and {@link #isReset()} will return the correct
173 * values, and then calls the implementation's {@link #onStartLoading()}.
174 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800175 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500176 */
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800177 public final void startLoading() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800178 mStarted = true;
179 mReset = false;
180 onStartLoading();
181 }
182
183 /**
184 * Subclasses must implement this to take care of loading their data,
185 * as per {@link #startLoading()}. This is not called by clients directly,
186 * but as a result of a call to {@link #startLoading()}.
187 */
188 protected void onStartLoading() {
189 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500190
191 /**
192 * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800193 * loaded data set and load a new one. This simply calls through to the
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800194 * implementation's {@link #onForceLoad()}. You generally should only call this
195 * when the loader is started -- that is, {@link #isStarted()} returns true.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800196 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800197 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500198 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800199 public void forceLoad() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800200 onForceLoad();
201 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500202
203 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800204 * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800205 * This will always be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500206 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800207 protected void onForceLoad() {
208 }
209
210 /**
211 * Stops delivery of updates until the next time {@link #startLoading()} is called.
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800212 * Implementations should <em>not</em> invalidate their data at this point --
213 * clients are still free to use the last data the loader reported. They will,
214 * however, typically stop reporting new data if the data changes; they can
215 * still monitor for changes, but must not report them to the client until and
216 * if {@link #startLoading()} is later called.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800217 *
218 * <p>This updates the Loader's internal state so that
219 * {@link #isStarted()} will return the correct
220 * value, and then calls the implementation's {@link #onStopLoading()}.
221 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800222 * <p>Must be called from the process's main thread.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800223 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800224 public void stopLoading() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800225 mStarted = false;
226 onStopLoading();
227 }
228
229 /**
230 * Subclasses must implement this to take care of stopping their loader,
231 * as per {@link #stopLoading()}. This is not called by clients directly,
232 * but as a result of a call to {@link #stopLoading()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800233 * This will always be called from the process's main thread.
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800234 */
235 protected void onStopLoading() {
236 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500237
238 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800239 * Resets the state of the Loader. The Loader should at this point free
240 * all of its resources, since it may never be called again; however, its
241 * {@link #startLoading()} may later be called at which point it must be
242 * able to start running again.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500243 *
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800244 * <p>This updates the Loader's internal state so that
245 * {@link #isStarted()} and {@link #isReset()} will return the correct
246 * values, and then calls the implementation's {@link #onReset()}.
247 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800248 * <p>Must be called from the process's main thread.
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500249 */
Dianne Hackborn247fe742011-01-08 17:25:57 -0800250 public void reset() {
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800251 onReset();
252 mReset = true;
253 mStarted = false;
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800254 mContentChanged = false;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800255 }
256
257 /**
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800258 * Subclasses must implement this to take care of resetting their loader,
259 * as per {@link #reset()}. This is not called by clients directly,
260 * but as a result of a call to {@link #reset()}.
Dianne Hackborn247fe742011-01-08 17:25:57 -0800261 * This will always be called from the process's main thread.
Dianne Hackbornc9189352010-12-15 14:57:25 -0800262 */
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800263 protected void onReset() {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800264 }
Makoto Onukiec37c882010-08-02 16:57:01 -0700265
266 /**
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800267 * Take the current flag indicating whether the loader's content had
268 * changed while it was stopped. If it had, true is returned and the
269 * flag is cleared.
270 */
271 public boolean takeContentChanged() {
272 boolean res = mContentChanged;
273 mContentChanged = false;
274 return res;
275 }
276
277 /**
278 * Called when {@link ForceLoadContentObserver} detects a change. The
279 * default implementation checks to see if the loader is currently started;
280 * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
281 * so that {@link #takeContentChanged()} returns true.
Makoto Onukiec37c882010-08-02 16:57:01 -0700282 *
Dianne Hackborn247fe742011-01-08 17:25:57 -0800283 * <p>Must be called from the process's main thread.
Makoto Onukiec37c882010-08-02 16:57:01 -0700284 */
285 public void onContentChanged() {
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800286 if (mStarted) {
287 forceLoad();
288 } else {
289 // This loader has been stopped, so we don't want to load
290 // new data right now... but keep track of it changing to
291 // refresh later if we start again.
292 mContentChanged = true;
293 }
Makoto Onukiec37c882010-08-02 16:57:01 -0700294 }
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800295
296 /**
297 * For debugging, converts an instance of the Loader's data class to
298 * a string that can be printed. Must handle a null data.
299 */
300 public String dataToString(D data) {
301 StringBuilder sb = new StringBuilder(64);
302 DebugUtils.buildShortClassTag(data, sb);
303 sb.append("}");
304 return sb.toString();
305 }
306
307 @Override
308 public String toString() {
309 StringBuilder sb = new StringBuilder(64);
310 DebugUtils.buildShortClassTag(this, sb);
311 sb.append(" id=");
312 sb.append(mId);
313 sb.append("}");
314 return sb.toString();
315 }
316
317 /**
318 * Print the Loader's state into the given stream.
319 *
320 * @param prefix Text to print at the front of each line.
321 * @param fd The raw file descriptor that the dump is being sent to.
322 * @param writer A PrintWriter to which the dump is to be set.
323 * @param args Additional arguments to the dump request.
324 */
325 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
326 writer.print(prefix); writer.print("mId="); writer.print(mId);
327 writer.print(" mListener="); writer.println(mListener);
328 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
Dianne Hackborn0e3b8f422010-12-20 23:22:11 -0800329 writer.print(" mContentChanged="); writer.print(mContentChanged);
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800330 writer.print(" mReset="); writer.println(mReset);
331 }
Jeff Hamilton9911b7f2010-05-15 02:20:31 -0500332}