switch to alsa.omap3 module
[android_pandora.git] / apps / AndroidSupportV2 / src / android / support / v2 / app / LoaderManager.java
CommitLineData
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
17package android.support.v2.app;
18
19import android.app.Activity;
20import android.os.Bundle;
21import android.support.v2.content.Loader;
22import android.support.v2.util.DebugUtils;
23import android.util.Log;
24
25import java.io.FileDescriptor;
26import java.io.PrintWriter;
27import java.lang.reflect.Modifier;
28
29/**
30 * Static library support version of the framework's {@link android.app.LoaderManager}.
31 * Used to write apps that run on platforms prior to Android 3.0. When running
32 * on Android 3.0 or above, this implementation is still used; it does not try
33 * to switch to the framework's implementation. See the framework SDK
34 * documentation for a class overview.
35 *
36 * <p>Your activity must derive from {@link FragmentActivity} to use this.
37 */
38public abstract class LoaderManager {
39 /**
40 * Callback interface for a client to interact with the manager.
41 */
42 public interface LoaderCallbacks<D> {
43 /**
44 * Instantiate and return a new Loader for the given ID.
45 *
46 * @param id The ID whose loader is to be created.
47 * @param args Any arguments supplied by the caller.
48 * @return Return a new Loader instance that is ready to start loading.
49 */
50 public Loader<D> onCreateLoader(int id, Bundle args);
51
52 /**
53 * Called when a previously created loader has finished its load. Note
54 * that normally an application is <em>not</em> allowed to commit fragment
55 * transactions while in this call, since it can happen after an
56 * activity's state is saved. See {@link FragmentManager#beginTransaction()
57 * FragmentManager.openTransaction()} for further discussion on this.
58 *
59 * <p>This function is guaranteed to be called prior to the release of
60 * the last data that was supplied for this Loader. At this point
61 * you should remove all use of the old data (since it will be released
62 * soon), but should not do your own release of the data since its Loader
63 * owns it and will take care of that. The Loader will take care of
64 * management of its data so you don't have to. In particular:
65 *
66 * <ul>
67 * <li> <p>The Loader will monitor for changes to the data, and report
68 * them to you through new calls here. You should not monitor the
69 * data yourself. For example, if the data is a {@link android.database.Cursor}
70 * and you place it in a {@link android.widget.CursorAdapter}, use
71 * the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
72 * android.database.Cursor, int)} constructor <em>without</em> passing
73 * in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
74 * or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
75 * (that is, use 0 for the flags argument). This prevents the CursorAdapter
76 * from doing its own observing of the Cursor, which is not needed since
77 * when a change happens you will get a new Cursor throw another call
78 * here.
79 * <li> The Loader will release the data once it knows the application
80 * is no longer using it. For example, if the data is
81 * a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
82 * you should not call close() on it yourself. If the Cursor is being placed in a
83 * {@link android.widget.CursorAdapter}, you should use the
84 * {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
85 * method so that the old Cursor is not closed.
86 * </ul>
87 *
88 * @param loader The Loader that has finished.
89 * @param data The data generated by the Loader.
90 */
91 public void onLoadFinished(Loader<D> loader, D data);
92
93 /**
94 * Called when a previously created loader is being reset, and thus
95 * making its data unavailable. The application should at this point
96 * remove any references it has to the Loader's data.
97 *
98 * @param loader The Loader that is being reset.
99 */
100 public void onLoaderReset(Loader<D> loader);
101 }
102
103 /**
104 * Ensures a loader is initialized and active. If the loader doesn't
105 * already exist, one is created and (if the activity/fragment is currently
106 * started) starts the loader. Otherwise the last created
107 * loader is re-used.
108 *
109 * <p>In either case, the given callback is associated with the loader, and
110 * will be called as the loader state changes. If at the point of call
111 * the caller is in its started state, and the requested loader
112 * already exists and has generated its data, then
113 * callback {@link LoaderCallbacks#onLoadFinished} will
114 * be called immediately (inside of this function), so you must be prepared
115 * for this to happen.
116 *
117 * @param id A unique identifier for this loader. Can be whatever you want.
118 * Identifiers are scoped to a particular LoaderManager instance.
119 * @param args Optional arguments to supply to the loader at construction.
120 * If a loader already exists (a new one does not need to be created), this
121 * parameter will be ignored and the last arguments continue to be used.
122 * @param callback Interface the LoaderManager will call to report about
123 * changes in the state of the loader. Required.
124 */
125 public abstract <D> Loader<D> initLoader(int id, Bundle args,
126 LoaderManager.LoaderCallbacks<D> callback);
127
128 /**
129 * Starts a new or restarts an existing {@link android.content.Loader} in
130 * this manager, registers the callbacks to it,
131 * and (if the activity/fragment is currently started) starts loading it.
132 * If a loader with the same id has previously been
133 * started it will automatically be destroyed when the new loader completes
134 * its work. The callback will be delivered before the old loader
135 * is destroyed.
136 *
137 * @param id A unique identifier for this loader. Can be whatever you want.
138 * Identifiers are scoped to a particular LoaderManager instance.
139 * @param args Optional arguments to supply to the loader at construction.
140 * @param callback Interface the LoaderManager will call to report about
141 * changes in the state of the loader. Required.
142 */
143 public abstract <D> Loader<D> restartLoader(int id, Bundle args,
144 LoaderManager.LoaderCallbacks<D> callback);
145
146 /**
147 * Stops and removes the loader with the given ID. If this loader
148 * had previously reported data to the client through
149 * {@link LoaderCallbacks#onLoadFinished(Loader, Object)}, a call
150 * will be made to {@link LoaderCallbacks#onLoaderReset(Loader)}.
151 */
152 public abstract void destroyLoader(int id);
153
154 /**
155 * Return the Loader with the given id or null if no matching Loader
156 * is found.
157 */
158 public abstract <D> Loader<D> getLoader(int id);
159
160 /**
161 * Print the LoaderManager's state into the given stream.
162 *
163 * @param prefix Text to print at the front of each line.
164 * @param fd The raw file descriptor that the dump is being sent to.
165 * @param writer A PrintWriter to which the dump is to be set.
166 * @param args Additional arguments to the dump request.
167 */
168 public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);
169
170 /**
171 * Control whether the framework's internal loader manager debugging
172 * logs are turned on. If enabled, you will see output in logcat as
173 * the framework performs loader operations.
174 */
175 public static void enableDebugLogging(boolean enabled) {
176 LoaderManagerImpl.DEBUG = enabled;
177 }
178}
179
180class LoaderManagerImpl extends LoaderManager {
181 static final String TAG = "LoaderManager";
182 static boolean DEBUG = false;
183
184 // These are the currently active loaders. A loader is here
185 // from the time its load is started until it has been explicitly
186 // stopped or restarted by the application.
187 final HCSparseArray<LoaderInfo> mLoaders = new HCSparseArray<LoaderInfo>();
188
189 // These are previously run loaders. This list is maintained internally
190 // to avoid destroying a loader while an application is still using it.
191 // It allows an application to restart a loader, but continue using its
192 // previously run loader until the new loader's data is available.
193 final HCSparseArray<LoaderInfo> mInactiveLoaders = new HCSparseArray<LoaderInfo>();
194
195 FragmentActivity mActivity;
196 boolean mStarted;
197 boolean mRetaining;
198 boolean mRetainingStarted;
199
200 boolean mCreatingLoader;
201
202 final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
203 final int mId;
204 final Bundle mArgs;
205 LoaderManager.LoaderCallbacks<Object> mCallbacks;
206 Loader<Object> mLoader;
207 boolean mHaveData;
208 boolean mDeliveredData;
209 Object mData;
210 boolean mStarted;
211 boolean mRetaining;
212 boolean mRetainingStarted;
213 boolean mDestroyed;
214 boolean mListenerRegistered;
215
216 LoaderInfo mPendingLoader;
217
218 public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
219 mId = id;
220 mArgs = args;
221 mCallbacks = callbacks;
222 }
223
224 void start() {
225 if (mRetaining && mRetainingStarted) {
226 // Our owner is started, but we were being retained from a
227 // previous instance in the started state... so there is really
228 // nothing to do here, since the loaders are still started.
229 mStarted = true;
230 return;
231 }
232
233 if (mStarted) {
234 // If loader already started, don't restart.
235 return;
236 }
237
238 mStarted = true;
239
240 if (DEBUG) Log.v(TAG, " Starting: " + this);
241 if (mLoader == null && mCallbacks != null) {
242 mLoader = mCallbacks.onCreateLoader(mId, mArgs);
243 }
244 if (mLoader != null) {
245 if (mLoader.getClass().isMemberClass()
246 && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
247 throw new IllegalArgumentException(
248 "Object returned from onCreateLoader must not be a non-static inner member class: "
249 + mLoader);
250 }
251 if (!mListenerRegistered) {
252 mLoader.registerListener(mId, this);
253 mListenerRegistered = true;
254 }
255 mLoader.startLoading();
256 }
257 }
258
259 void retain() {
260 if (DEBUG) Log.v(TAG, " Retaining: " + this);
261 mRetaining = true;
262 mRetainingStarted = mStarted;
263 mStarted = false;
264 mCallbacks = null;
265 }
266
267 void finishRetain() {
268 if (mRetaining) {
269 if (DEBUG) Log.v(TAG, " Finished Retaining: " + this);
270 mRetaining = false;
271 if (mStarted != mRetainingStarted) {
272 if (!mStarted) {
273 // This loader was retained in a started state, but
274 // at the end of retaining everything our owner is
275 // no longer started... so make it stop.
276 stop();
277 }
278 }
279 }
280
281 if (mStarted && mHaveData) {
282 // This loader has retained its data, either completely across
283 // a configuration change or just whatever the last data set
284 // was after being restarted from a stop, and now at the point of
285 // finishing the retain we find we remain started, have
286 // our data, and the owner has a new callback... so
287 // let's deliver the data now.
288 callOnLoadFinished(mLoader, mData);
289 }
290 }
291
292 void stop() {
293 if (DEBUG) Log.v(TAG, " Stopping: " + this);
294 mStarted = false;
295 if (!mRetaining) {
296 if (mLoader != null && mListenerRegistered) {
297 // Let the loader know we're done with it
298 mListenerRegistered = false;
299 mLoader.unregisterListener(this);
300 mLoader.stopLoading();
301 }
302 }
303 }
304
305 void destroy() {
306 if (DEBUG) Log.v(TAG, " Destroying: " + this);
307 mDestroyed = true;
308 boolean needReset = mDeliveredData;
309 mDeliveredData = false;
310 if (mCallbacks != null && mLoader != null && mHaveData && needReset) {
311 if (DEBUG) Log.v(TAG, " Reseting: " + this);
312 String lastBecause = null;
313 if (mActivity != null) {
314 lastBecause = mActivity.mFragments.mNoTransactionsBecause;
315 mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset";
316 }
317 try {
318 mCallbacks.onLoaderReset(mLoader);
319 } finally {
320 if (mActivity != null) {
321 mActivity.mFragments.mNoTransactionsBecause = lastBecause;
322 }
323 }
324 }
325 mCallbacks = null;
326 mData = null;
327 mHaveData = false;
328 if (mLoader != null) {
329 if (mListenerRegistered) {
330 mListenerRegistered = false;
331 mLoader.unregisterListener(this);
332 }
333 mLoader.reset();
334 }
335 if (mPendingLoader != null) {
336 mPendingLoader.destroy();
337 }
338 }
339
340 @Override public void onLoadComplete(Loader<Object> loader, Object data) {
341 if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
342
343 if (mDestroyed) {
344 if (DEBUG) Log.v(TAG, " Ignoring load complete -- destroyed");
345 return;
346 }
347
348 if (mLoaders.get(mId) != this) {
349 // This data is not coming from the current active loader.
350 // We don't care about it.
351 if (DEBUG) Log.v(TAG, " Ignoring load complete -- not active");
352 return;
353 }
354
355 LoaderInfo pending = mPendingLoader;
356 if (pending != null) {
357 // There is a new request pending and we were just
358 // waiting for the old one to complete before starting
359 // it. So now it is time, switch over to the new loader.
360 if (DEBUG) Log.v(TAG, " Switching to pending loader: " + pending);
361 mPendingLoader = null;
362 mLoaders.put(mId, null);
363 destroy();
364 installLoader(pending);
365 return;
366 }
367
368 // Notify of the new data so the app can switch out the old data before
369 // we try to destroy it.
370 if (mData != data || !mHaveData) {
371 mData = data;
372 mHaveData = true;
373 if (mStarted) {
374 callOnLoadFinished(loader, data);
375 }
376 }
377
378 //if (DEBUG) Log.v(TAG, " onLoadFinished returned: " + this);
379
380 // We have now given the application the new loader with its
381 // loaded data, so it should have stopped using the previous
382 // loader. If there is a previous loader on the inactive list,
383 // clean it up.
384 LoaderInfo info = mInactiveLoaders.get(mId);
385 if (info != null && info != this) {
386 info.mDeliveredData = false;
387 info.destroy();
388 mInactiveLoaders.remove(mId);
389 }
390 }
391
392 void callOnLoadFinished(Loader<Object> loader, Object data) {
393 if (mCallbacks != null) {
394 String lastBecause = null;
395 if (mActivity != null) {
396 lastBecause = mActivity.mFragments.mNoTransactionsBecause;
397 mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished";
398 }
399 try {
400 if (DEBUG) Log.v(TAG, " onLoadFinished in " + loader + ": "
401 + loader.dataToString(data));
402 mCallbacks.onLoadFinished(loader, data);
403 } finally {
404 if (mActivity != null) {
405 mActivity.mFragments.mNoTransactionsBecause = lastBecause;
406 }
407 }
408 mDeliveredData = true;
409 }
410 }
411
412 @Override
413 public String toString() {
414 StringBuilder sb = new StringBuilder(64);
415 sb.append("LoaderInfo{");
416 sb.append(Integer.toHexString(System.identityHashCode(this)));
417 sb.append(" #");
418 sb.append(mId);
419 sb.append(" : ");
420 DebugUtils.buildShortClassTag(mLoader, sb);
421 sb.append("}}");
422 return sb.toString();
423 }
424
425 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
426 writer.print(prefix); writer.print("mId="); writer.print(mId);
427 writer.print(" mArgs="); writer.println(mArgs);
428 writer.print(prefix); writer.print("mCallbacks="); writer.println(mCallbacks);
429 writer.print(prefix); writer.print("mLoader="); writer.println(mLoader);
430 if (mLoader != null) {
431 mLoader.dump(prefix + " ", fd, writer, args);
432 }
433 if (mHaveData || mDeliveredData) {
434 writer.print(prefix); writer.print("mHaveData="); writer.print(mHaveData);
435 writer.print(" mDeliveredData="); writer.println(mDeliveredData);
436 writer.print(prefix); writer.print("mData="); writer.println(mData);
437 }
438 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
439 writer.print(" mRetaining="); writer.print(mRetaining);
440 writer.print(" mDestroyed="); writer.println(mDestroyed);
441 writer.print(prefix); writer.print("mListenerRegistered=");
442 writer.println(mListenerRegistered);
443 if (mPendingLoader != null) {
444 writer.print(prefix); writer.println("Pending Loader ");
445 writer.print(mPendingLoader); writer.println(":");
446 mPendingLoader.dump(prefix + " ", fd, writer, args);
447 }
448 }
449 }
450
451 LoaderManagerImpl(FragmentActivity activity, boolean started) {
452 mActivity = activity;
453 mStarted = started;
454 }
455
456 void updateActivity(FragmentActivity activity) {
457 mActivity = activity;
458 }
459
460 private LoaderInfo createLoader(int id, Bundle args,
461 LoaderManager.LoaderCallbacks<Object> callback) {
462 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
463 Loader<Object> loader = callback.onCreateLoader(id, args);
464 info.mLoader = (Loader<Object>)loader;
465 return info;
466 }
467
468 private LoaderInfo createAndInstallLoader(int id, Bundle args,
469 LoaderManager.LoaderCallbacks<Object> callback) {
470 try {
471 mCreatingLoader = true;
472 LoaderInfo info = createLoader(id, args, callback);
473 installLoader(info);
474 return info;
475 } finally {
476 mCreatingLoader = false;
477 }
478 }
479
480 void installLoader(LoaderInfo info) {
481 mLoaders.put(info.mId, info);
482 if (mStarted) {
483 // The activity will start all existing loaders in it's onStart(),
484 // so only start them here if we're past that point of the activitiy's
485 // life cycle
486 info.start();
487 }
488 }
489
490 /**
491 * Call to initialize a particular ID with a Loader. If this ID already
492 * has a Loader associated with it, it is left unchanged and any previous
493 * callbacks replaced with the newly provided ones. If there is not currently
494 * a Loader for the ID, a new one is created and started.
495 *
496 * <p>This function should generally be used when a component is initializing,
497 * to ensure that a Loader it relies on is created. This allows it to re-use
498 * an existing Loader's data if there already is one, so that for example
499 * when an {@link Activity} is re-created after a configuration change it
500 * does not need to re-create its loaders.
501 *
502 * <p>Note that in the case where an existing Loader is re-used, the
503 * <var>args</var> given here <em>will be ignored</em> because you will
504 * continue using the previous Loader.
505 *
506 * @param id A unique (to this LoaderManager instance) identifier under
507 * which to manage the new Loader.
508 * @param args Optional arguments that will be propagated to
509 * {@link LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}.
510 * @param callback Interface implementing management of this Loader. Required.
511 * Its onCreateLoader() method will be called while inside of the function to
512 * instantiate the Loader object.
513 */
514 @SuppressWarnings("unchecked")
515 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
516 if (mCreatingLoader) {
517 throw new IllegalStateException("Called while creating a loader");
518 }
519
520 LoaderInfo info = mLoaders.get(id);
521
522 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
523
524 if (info == null) {
525 // Loader doesn't already exist; create.
526 info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
527 if (DEBUG) Log.v(TAG, " Created new loader " + info);
528 } else {
529 if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
530 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
531 }
532
533 if (info.mHaveData && mStarted) {
534 // If the loader has already generated its data, report it now.
535 info.callOnLoadFinished(info.mLoader, info.mData);
536 }
537
538 return (Loader<D>)info.mLoader;
539 }
540
541 /**
542 * Call to re-create the Loader associated with a particular ID. If there
543 * is currently a Loader associated with this ID, it will be
544 * canceled/stopped/destroyed as appropriate. A new Loader with the given
545 * arguments will be created and its data delivered to you once available.
546 *
547 * <p>This function does some throttling of Loaders. If too many Loaders
548 * have been created for the given ID but not yet generated their data,
549 * new calls to this function will create and return a new Loader but not
550 * actually start it until some previous loaders have completed.
551 *
552 * <p>After calling this function, any previous Loaders associated with
553 * this ID will be considered invalid, and you will receive no further
554 * data updates from them.
555 *
556 * @param id A unique (to this LoaderManager instance) identifier under
557 * which to manage the new Loader.
558 * @param args Optional arguments that will be propagated to
559 * {@link LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}.
560 * @param callback Interface implementing management of this Loader. Required.
561 * Its onCreateLoader() method will be called while inside of the function to
562 * instantiate the Loader object.
563 */
564 @SuppressWarnings("unchecked")
565 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
566 if (mCreatingLoader) {
567 throw new IllegalStateException("Called while creating a loader");
568 }
569
570 LoaderInfo info = mLoaders.get(id);
571 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args);
572 if (info != null) {
573 LoaderInfo inactive = mInactiveLoaders.get(id);
574 if (inactive != null) {
575 if (info.mHaveData) {
576 // This loader now has data... we are probably being
577 // called from within onLoadComplete, where we haven't
578 // yet destroyed the last inactive loader. So just do
579 // that now.
580 if (DEBUG) Log.v(TAG, " Removing last inactive loader: " + info);
581 inactive.mDeliveredData = false;
582 inactive.destroy();
583 info.mLoader.abandon();
584 mInactiveLoaders.put(id, info);
585 } else {
586 // We already have an inactive loader for this ID that we are
587 // waiting for! What to do, what to do...
588 if (!info.mStarted) {
589 // The current Loader has not been started... we thus
590 // have no reason to keep it around, so bam, slam,
591 // thank-you-ma'am.
592 if (DEBUG) Log.v(TAG, " Current loader is stopped; replacing");
593 mLoaders.put(id, null);
594 info.destroy();
595 } else {
596 // Now we have three active loaders... we'll queue
597 // up this request to be processed once one of the other loaders
598 // finishes.
599 if (info.mPendingLoader != null) {
600 if (DEBUG) Log.v(TAG, " Removing pending loader: " + info.mPendingLoader);
601 info.mPendingLoader.destroy();
602 info.mPendingLoader = null;
603 }
604 if (DEBUG) Log.v(TAG, " Enqueuing as new pending loader");
605 info.mPendingLoader = createLoader(id, args,
606 (LoaderManager.LoaderCallbacks<Object>)callback);
607 return (Loader<D>)info.mPendingLoader.mLoader;
608 }
609 }
610 } else {
611 // Keep track of the previous instance of this loader so we can destroy
612 // it when the new one completes.
613 if (DEBUG) Log.v(TAG, " Making last loader inactive: " + info);
614 info.mLoader.abandon();
615 mInactiveLoaders.put(id, info);
616 }
617 }
618
619 info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
620 return (Loader<D>)info.mLoader;
621 }
622
623 /**
624 * Rip down, tear apart, shred to pieces a current Loader ID. After returning
625 * from this function, any Loader objects associated with this ID are
626 * destroyed. Any data associated with them is destroyed. You better not
627 * be using it when you do this.
628 * @param id Identifier of the Loader to be destroyed.
629 */
630 public void destroyLoader(int id) {
631 if (mCreatingLoader) {
632 throw new IllegalStateException("Called while creating a loader");
633 }
634
635 if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id);
636 int idx = mLoaders.indexOfKey(id);
637 if (idx >= 0) {
638 LoaderInfo info = mLoaders.valueAt(idx);
639 mLoaders.removeAt(idx);
640 info.destroy();
641 }
642 idx = mInactiveLoaders.indexOfKey(id);
643 if (idx >= 0) {
644 LoaderInfo info = mInactiveLoaders.valueAt(idx);
645 mInactiveLoaders.removeAt(idx);
646 info.destroy();
647 }
648 }
649
650 /**
651 * Return the most recent Loader object associated with the
652 * given ID.
653 */
654 @SuppressWarnings("unchecked")
655 public <D> Loader<D> getLoader(int id) {
656 if (mCreatingLoader) {
657 throw new IllegalStateException("Called while creating a loader");
658 }
659
660 LoaderInfo loaderInfo = mLoaders.get(id);
661 if (loaderInfo != null) {
662 if (loaderInfo.mPendingLoader != null) {
663 return (Loader<D>)loaderInfo.mPendingLoader.mLoader;
664 }
665 return (Loader<D>)loaderInfo.mLoader;
666 }
667 return null;
668 }
669
670 void doStart() {
671 if (DEBUG) Log.v(TAG, "Starting in " + this);
672 if (mStarted) {
673 RuntimeException e = new RuntimeException("here");
674 e.fillInStackTrace();
675 Log.w(TAG, "Called doStart when already started: " + this, e);
676 return;
677 }
678
679 mStarted = true;
680
681 // Call out to sub classes so they can start their loaders
682 // Let the existing loaders know that we want to be notified when a load is complete
683 for (int i = mLoaders.size()-1; i >= 0; i--) {
684 mLoaders.valueAt(i).start();
685 }
686 }
687
688 void doStop() {
689 if (DEBUG) Log.v(TAG, "Stopping in " + this);
690 if (!mStarted) {
691 RuntimeException e = new RuntimeException("here");
692 e.fillInStackTrace();
693 Log.w(TAG, "Called doStop when not started: " + this, e);
694 return;
695 }
696
697 for (int i = mLoaders.size()-1; i >= 0; i--) {
698 mLoaders.valueAt(i).stop();
699 }
700 mStarted = false;
701 }
702
703 void doRetain() {
704 if (DEBUG) Log.v(TAG, "Retaining in " + this);
705 if (!mStarted) {
706 RuntimeException e = new RuntimeException("here");
707 e.fillInStackTrace();
708 Log.w(TAG, "Called doRetain when not started: " + this, e);
709 return;
710 }
711
712 mRetaining = true;
713 mStarted = false;
714 for (int i = mLoaders.size()-1; i >= 0; i--) {
715 mLoaders.valueAt(i).retain();
716 }
717 }
718
719 void finishRetain() {
720 if (mRetaining) {
721 if (DEBUG) Log.v(TAG, "Finished Retaining in " + this);
722
723 mRetaining = false;
724 for (int i = mLoaders.size()-1; i >= 0; i--) {
725 mLoaders.valueAt(i).finishRetain();
726 }
727 }
728 }
729
730 void doDestroy() {
731 if (!mRetaining) {
732 if (DEBUG) Log.v(TAG, "Destroying Active in " + this);
733 for (int i = mLoaders.size()-1; i >= 0; i--) {
734 mLoaders.valueAt(i).destroy();
735 }
736 }
737
738 if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this);
739 for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
740 mInactiveLoaders.valueAt(i).destroy();
741 }
742 mInactiveLoaders.clear();
743 }
744
745 @Override
746 public String toString() {
747 StringBuilder sb = new StringBuilder(128);
748 sb.append("LoaderManager{");
749 sb.append(Integer.toHexString(System.identityHashCode(this)));
750 sb.append(" in ");
751 DebugUtils.buildShortClassTag(mActivity, sb);
752 sb.append("}}");
753 return sb.toString();
754 }
755
756 @Override
757 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
758 if (mLoaders.size() > 0) {
759 writer.print(prefix); writer.println("Active Loaders:");
760 String innerPrefix = prefix + " ";
761 for (int i=0; i < mLoaders.size(); i++) {
762 LoaderInfo li = mLoaders.valueAt(i);
763 writer.print(prefix); writer.print(" #"); writer.print(mLoaders.keyAt(i));
764 writer.print(": "); writer.println(li.toString());
765 li.dump(innerPrefix, fd, writer, args);
766 }
767 }
768 if (mInactiveLoaders.size() > 0) {
769 writer.print(prefix); writer.println("Inactive Loaders:");
770 String innerPrefix = prefix + " ";
771 for (int i=0; i < mInactiveLoaders.size(); i++) {
772 LoaderInfo li = mInactiveLoaders.valueAt(i);
773 writer.print(prefix); writer.print(" #"); writer.print(mInactiveLoaders.keyAt(i));
774 writer.print(": "); writer.println(li.toString());
775 li.dump(innerPrefix, fd, writer, args);
776 }
777 }
778 }
779}