Integrate Loader support in to Activity/Fragment.

Introduces a new LoaderManager class that takes care of
most of what LoaderManagingFragment does.  Every Fragment
and Activity can have one instance of this class.  In the
future, the instance will be retained across config changes.

Also various other cleanups and improvement.

Change-Id: I3dfb406dca46bda7f5acb3c722efcbfb8d0aa9ba
diff --git a/api/current.xml b/api/current.xml
index 4d86a04..d75a141 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -20509,6 +20509,17 @@
  visibility="public"
 >
 </method>
+<method name="getLoaderManager"
+ return="android.app.LoaderManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getLocalClassName"
  return="java.lang.String"
  abstract="false"
@@ -20760,6 +20771,19 @@
 <parameter name="data" type="android.content.Intent">
 </parameter>
 </method>
+<method name="onAttachFragment"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
 <method name="onAttachedToWindow"
  return="void"
  abstract="false"
@@ -25977,6 +26001,17 @@
  visibility="public"
 >
 </method>
+<method name="getLoaderManager"
+ return="android.app.LoaderManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getRetainInstance"
  return="boolean"
  abstract="false"
@@ -26054,6 +26089,19 @@
  visibility="public"
 >
 </method>
+<method name="onActivityCreated"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
 <method name="onActivityResult"
  return="void"
  abstract="false"
@@ -26313,19 +26361,6 @@
 <parameter name="menu" type="android.view.Menu">
 </parameter>
 </method>
-<method name="onReady"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="savedInstanceState" type="android.os.Bundle">
-</parameter>
-</method>
 <method name="onResume"
  return="void"
  abstract="false"
@@ -28436,6 +28471,96 @@
 </parameter>
 </method>
 </class>
+<class name="LoaderManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+<method name="startLoading"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+<parameter name="callback" type="android.app.LoaderManager.LoaderCallbacks&lt;D&gt;">
+</parameter>
+</method>
+<method name="stopLoading"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+</class>
+<interface name="LoaderManager.LoaderCallbacks"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onCreateLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onLoadFinished"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="loader" type="android.content.Loader&lt;D&gt;">
+</parameter>
+<parameter name="data" type="D">
+</parameter>
+</method>
+</interface>
 <class name="LoaderManagingFragment"
  extends="android.app.Fragment"
  abstract="true"
@@ -174029,6 +174154,19 @@
 <parameter name="key" type="int">
 </parameter>
 </method>
+<method name="removeAt"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
 <method name="setValueAt"
  return="void"
  abstract="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4166b89..d846610 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -649,6 +649,7 @@
         Object activity;
         HashMap<String, Object> children;
         ArrayList<Fragment> fragments;
+        SparseArray<LoaderManager> loaders;
     }
     /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
     
@@ -666,6 +667,9 @@
 
     final FragmentManager mFragments = new FragmentManager();
     
+    SparseArray<LoaderManager> mAllLoaderManagers;
+    LoaderManager mLoaderManager;
+    
     private static final class ManagedCursor {
         ManagedCursor(Cursor cursor) {
             mCursor = cursor;
@@ -763,6 +767,29 @@
     }
 
     /**
+     * Return the LoaderManager for this fragment, creating it if needed.
+     */
+    public LoaderManager getLoaderManager() {
+        if (mLoaderManager != null) {
+            return mLoaderManager;
+        }
+        mLoaderManager = getLoaderManager(-1, false);
+        return mLoaderManager;
+    }
+    
+    LoaderManager getLoaderManager(int index, boolean started) {
+        if (mAllLoaderManagers == null) {
+            mAllLoaderManagers = new SparseArray<LoaderManager>();
+        }
+        LoaderManager lm = mAllLoaderManagers.get(index);
+        if (lm == null) {
+            lm = new LoaderManager(started);
+            mAllLoaderManagers.put(index, lm);
+        }
+        return lm;
+    }
+    
+    /**
      * Calls {@link android.view.Window#getCurrentFocus} on the
      * Window of this Activity to return the currently focused view.
      * 
@@ -1519,6 +1546,14 @@
     }
     
     /**
+     * Called when a Fragment is being attached to this activity, immediately
+     * after the call to its {@link Fragment#onAttach Fragment.onAttach()}
+     * method and before {@link Fragment#onCreate Fragment.onCreate()}.
+     */
+    public void onAttachFragment(Fragment fragment) {
+    }
+    
+    /**
      * Wrapper around
      * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
      * that gives the resulting {@link Cursor} to call
@@ -2060,13 +2095,6 @@
     }
 
     public void onContentChanged() {
-        // First time content is available, let the fragment manager
-        // attach all of the fragments to it.  Don't do this if the
-        // activity is no longer attached (because it is being destroyed).
-        if (mFragments.mCurState < Fragment.CONTENT
-                && mFragments.mActivity != null) {
-            mFragments.moveToState(Fragment.CONTENT, false);
-        }
     }
 
     /**
@@ -4024,6 +4052,7 @@
 
     final void performCreate(Bundle icicle) {
         onCreate(icicle);
+        mFragments.dispatchActivityCreated();
     }
     
     final void performStart() {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 231d6ab..da3072c 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -134,14 +134,14 @@
  * that can be placed in an {@link Activity}.
  */
 public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {
-    private static final HashMap<String, Class> sClassMap =
-            new HashMap<String, Class>();
+    private static final HashMap<String, Class<?>> sClassMap =
+            new HashMap<String, Class<?>>();
     
-    static final int INITIALIZING = 0;  // Not yet created.
-    static final int CREATED = 1;       // Created.
-    static final int CONTENT = 2;       // View hierarchy content available.
-    static final int STARTED = 3;       // Created and started, not resumed.
-    static final int RESUMED = 4;       // Created started and resumed.
+    static final int INITIALIZING = 0;     // Not yet created.
+    static final int CREATED = 1;          // Created.
+    static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
+    static final int STARTED = 3;          // Created and started, not resumed.
+    static final int RESUMED = 4;          // Created started and resumed.
     
     int mState = INITIALIZING;
     
@@ -210,6 +210,17 @@
     // The View generated for this fragment.
     View mView;
     
+    LoaderManager mLoaderManager;
+    boolean mStarted;
+    
+    /**
+     * Default constructor.  <strong>Every</string> fragment must have an
+     * empty constructor, so it can be instantiated when restoring its
+     * activity's state.  It is strongly recommended that subclasses do not
+     * have other constructors with parameters, since these constructors
+     * will not be called when the fragment is re-instantiated; instead,
+     * retrieve such parameters from the activity in {@link #onAttach(Activity)}.
+     */
     public Fragment() {
     }
 
@@ -217,7 +228,7 @@
             throws NoSuchMethodException, ClassNotFoundException,
             IllegalArgumentException, InstantiationException,
             IllegalAccessException, InvocationTargetException {
-        Class clazz = sClassMap.get(fname);
+        Class<?> clazz = sClassMap.get(fname);
 
         if (clazz == null) {
             // Class not found in the cache, see if it's real, and try to add it
@@ -350,7 +361,7 @@
      * will be, because the fragment is being detached from its current activity).
      * <li> {@link #onCreate(Bundle)} will not be called since the fragment
      * is not being re-created.
-     * <li> {@link #onAttach(Activity)} and {@link #onReady(Bundle)} <b>will</b>
+     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
      * still be called.
      * </ul>
      */
@@ -379,6 +390,17 @@
     }
     
     /**
+     * Return the LoaderManager for this fragment, creating it if needed.
+     */
+    public LoaderManager getLoaderManager() {
+        if (mLoaderManager != null) {
+            return mLoaderManager;
+        }
+        mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted);
+        return mLoaderManager;
+    }
+    
+    /**
      * Call {@link Activity#startActivity(Intent)} on the fragment's
      * containing Activity.
      */
@@ -446,7 +468,15 @@
     
     /**
      * Called to do initial creation of a fragment.  This is called after
-     * {@link #onAttach(Activity)} and before {@link #onReady(Bundle)}.
+     * {@link #onAttach(Activity)} and before
+     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     * 
+     * <p>Note that this can be called while the fragment's activity is
+     * still in the process of being created.  As such, you can not rely
+     * on things like the activity's content view hierarchy being initialized
+     * at this point.  If you want to do work once the activity itself is
+     * created, see {@link #onActivityCreated(Bundle)}.
+     * 
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
@@ -458,7 +488,7 @@
      * Called to have the fragment instantiate its user interface view.
      * This is optional, and non-graphical fragments can return null (which
      * is the default implementation).  This will be called between
-     * {@link #onCreate(Bundle)} and {@link #onReady(Bundle)}.
+     * {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}.
      * 
      * <p>If you return a View from here, you will later be called in
      * {@link #onDestroyView} when the view is being released.
@@ -483,16 +513,19 @@
     }
     
     /**
-     * Called when the activity is ready for the fragment to run.  This is
-     * most useful for fragments that use {@link #setRetainInstance(boolean)}
-     * instance, as this tells the fragment when it is fully associated with
+     * Called when the fragment's activity has been created and this
+     * fragment's view hierarchy instantiated.  It can be used to do final
+     * initialization once these pieces are in place, such as retrieving
+     * views or restoring state.  It is also useful for fragments that use
+     * {@link #setRetainInstance(boolean)} to retain their instance,
+     * as this callback tells the fragment when it is fully associated with
      * the new activity instance.  This is called after {@link #onCreateView}
      * and before {@link #onStart()}.
      * 
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
-    public void onReady(Bundle savedInstanceState) {
+    public void onActivityCreated(Bundle savedInstanceState) {
         mCalled = true;
     }
     
@@ -503,6 +536,10 @@
      */
     public void onStart() {
         mCalled = true;
+        mStarted = true;
+        if (mLoaderManager != null) {
+            mLoaderManager.doStart();
+        }
     }
     
     /**
@@ -538,6 +575,10 @@
      */
     public void onStop() {
         mCalled = true;
+        mStarted = false;
+        if (mLoaderManager != null) {
+            mLoaderManager.doStop();
+        }
     }
     
     public void onLowMemory() {
@@ -561,6 +602,9 @@
      */
     public void onDestroy() {
         mCalled = true;
+        if (mLoaderManager != null) {
+            mLoaderManager.doDestroy();
+        }
     }
 
     /**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index b8eeb094..b324dfb 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -162,6 +162,7 @@
                         throw new SuperNotCalledException("Fragment " + f
                                 + " did not call through to super.onAttach()");
                     }
+                    mActivity.onAttachFragment(f);
                     
                     if (!f.mRetaining) {
                         f.mCalled = false;
@@ -216,15 +217,15 @@
                         }
                         
                         f.mCalled = false;
-                        f.onReady(f.mSavedFragmentState);
+                        f.onActivityCreated(f.mSavedFragmentState);
                         if (!f.mCalled) {
                             throw new SuperNotCalledException("Fragment " + f
                                     + " did not call through to super.onReady()");
                         }
                         f.mSavedFragmentState = null;
                     }
-                case Fragment.CONTENT:
-                    if (newState > Fragment.CONTENT) {
+                case Fragment.ACTIVITY_CREATED:
+                    if (newState > Fragment.ACTIVITY_CREATED) {
                         if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                         f.mCalled = false;
                         f.onStart();
@@ -266,8 +267,8 @@
                                     + " did not call through to super.onStop()");
                         }
                     }
-                case Fragment.CONTENT:
-                    if (newState < Fragment.CONTENT) {
+                case Fragment.ACTIVITY_CREATED:
+                    if (newState < Fragment.ACTIVITY_CREATED) {
                         if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
                         if (f.mView != null) {
                             f.mCalled = false;
@@ -783,6 +784,10 @@
         moveToState(Fragment.CREATED, false);
     }
     
+    public void dispatchActivityCreated() {
+        moveToState(Fragment.ACTIVITY_CREATED, false);
+    }
+    
     public void dispatchStart() {
         moveToState(Fragment.STARTED, false);
     }
@@ -796,7 +801,7 @@
     }
     
     public void dispatchStop() {
-        moveToState(Fragment.CONTENT, false);
+        moveToState(Fragment.ACTIVITY_CREATED, false);
     }
     
     public void dispatchDestroy() {
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index a6be329..96485f7 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -160,7 +160,6 @@
     TextView mStandardEmptyView;
     View mProgressContainer;
     View mListContainer;
-    boolean mSetEmptyView;
     boolean mListShown;
 
     public ListFragment() {
@@ -185,8 +184,8 @@
      * Attach to list view once Fragment is ready to run.
      */
     @Override
-    public void onReady(Bundle savedInstanceState) {
-        super.onReady(savedInstanceState);
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
         ensureList();
     }
 
@@ -269,10 +268,7 @@
         if (mStandardEmptyView == null) {
             throw new IllegalStateException("Can't be used with a custom content view");
         }
-        if (!mSetEmptyView) {
-            mSetEmptyView = true;
-            mList.setEmptyView(mStandardEmptyView);
-        }
+        mList.setEmptyView(mStandardEmptyView);
     }
     
     /**
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
new file mode 100644
index 0000000..998dae9
--- /dev/null
+++ b/core/java/android/app/LoaderManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.os.Bundle;
+import android.util.SparseArray;
+
+/**
+ * Object associated with an {@link Activity} or {@link Fragment} for managing
+ * one or more {@link android.content.Loader} instances associated with it.
+ */
+public class LoaderManager {
+    /**
+     * Callback interface for a client to interact with the manager.
+     */
+    public interface LoaderCallbacks<D> {
+        public Loader<D> onCreateLoader(int id, Bundle args);
+        public void onLoadFinished(Loader<D> loader, D data);
+    }
+    
+    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
+        public Bundle args;
+        public Loader<Object> loader;
+        public LoaderManager.LoaderCallbacks<Object> callback;
+        
+        @Override public void onLoadComplete(Loader<Object> loader, Object data) {
+            // Notify of the new data so the app can switch out the old data before
+            // we try to destroy it.
+            callback.onLoadFinished(loader, data);
+
+            // Look for an inactive loader and destroy it if found
+            int id = loader.getId();
+            LoaderInfo info = mInactiveLoaders.get(id);
+            if (info != null) {
+                Loader<Object> oldLoader = info.loader;
+                if (oldLoader != null) {
+                    oldLoader.destroy();
+                }
+                mInactiveLoaders.remove(id);
+            }
+        }
+    }
+
+    SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
+    SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
+    boolean mStarted;
+    
+    LoaderManager(boolean started) {
+        mStarted = started;
+    }
+    
+    /**
+     * Associates a loader with this managers, registers the callbacks on it,
+     * and starts it loading.  If a loader with the same id has previously been
+     * started it will automatically be destroyed when the new loader completes
+     * its work. The callback will be delivered before the old loader
+     * is destroyed.
+     */
+    @SuppressWarnings("unchecked")
+    public <D> Loader<D> startLoading(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
+        LoaderInfo info = mLoaders.get(id);
+        if (info != null) {
+            // Keep track of the previous instance of this loader so we can destroy
+            // it when the new one completes.
+            mInactiveLoaders.put(id, info);
+        }
+        info = new LoaderInfo();
+        info.args = args;
+        info.callback = (LoaderManager.LoaderCallbacks<Object>)callback;
+        mLoaders.put(id, info);
+        Loader<D> loader = callback.onCreateLoader(id, args);
+        info.loader = (Loader<Object>)loader;
+        if (mStarted) {
+            // The activity will start all existing loaders in it's onStart(), so only start them
+            // here if we're past that point of the activitiy's life cycle
+            loader.registerListener(id, (OnLoadCompleteListener<D>)info);
+            loader.startLoading();
+        }
+        return loader;
+        
+    }
+    
+    /**
+     * Stops and removes the loader with the given ID.
+     */
+    public void stopLoading(int id) {
+        if (mLoaders != null) {
+            int idx = mLoaders.indexOfKey(id);
+            if (idx >= 0) {
+                LoaderInfo info = mLoaders.valueAt(idx);
+                mLoaders.removeAt(idx);
+                Loader<Object> loader = info.loader;
+                if (loader != null) {
+                    loader.unregisterListener(info);
+                    loader.destroy();
+                }
+            }
+        }
+    }
+
+    /**
+     * @param <D>
+     * @return the Loader with the given id or null if no matching Loader
+     * is found.
+     */
+    @SuppressWarnings("unchecked")
+    public <D> Loader<D> getLoader(int id) {
+        LoaderInfo loaderInfo = mLoaders.get(id);
+        if (loaderInfo != null) {
+            return (Loader<D>)mLoaders.get(id).loader;
+        }
+        return null;
+    }
+ 
+    void doStart() {
+        // Call out to sub classes so they can start their loaders
+        // Let the existing loaders know that we want to be notified when a load is complete
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            LoaderInfo info = mLoaders.valueAt(i);
+            Loader<Object> loader = info.loader;
+            int id = mLoaders.keyAt(i);
+            if (loader == null) {
+               loader = info.callback.onCreateLoader(id, info.args);
+               info.loader = loader;
+            }
+            loader.registerListener(id, info);
+            loader.startLoading();
+        }
+
+        mStarted = true;
+    }
+    
+    void doStop() {
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            LoaderInfo info = mLoaders.valueAt(i);
+            Loader<Object> loader = info.loader;
+            if (loader == null) {
+                continue;
+            }
+
+            // Let the loader know we're done with it
+            loader.unregisterListener(info);
+
+            // The loader isn't getting passed along to the next instance so ask it to stop loading
+            //if (!getActivity().isChangingConfigurations()) {
+            //    loader.stopLoading();
+            //}
+        }
+
+        mStarted = false;
+    }
+    
+    void doDestroy() {
+        if (mLoaders != null) {
+            for (int i = mLoaders.size()-1; i >= 0; i--) {
+                LoaderInfo info = mLoaders.valueAt(i);
+                Loader<Object> loader = info.loader;
+                if (loader == null) {
+                    continue;
+                }
+                loader.destroy();
+            }
+        }
+    }
+}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 1c8b330..7fc43b9 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -90,6 +90,16 @@
         delete(key);
     }
 
+    /**
+     * Removes the mapping at the specified index.
+     */
+    public void removeAt(int index) {
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+    
     private void gc() {
         // Log.e("SparseArray", "gc start with " + mSize);