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