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