Copying (and modifying) some files from packages/experimental

Change-Id: I00ba3c5b05df4320db51cc659fd84f6b37486d6d
diff --git a/src/android/app/patterns/AsyncTaskLoader.java b/src/android/app/patterns/AsyncTaskLoader.java
index 01b3e24..84effaf 100644
--- a/src/android/app/patterns/AsyncTaskLoader.java
+++ b/src/android/app/patterns/AsyncTaskLoader.java
@@ -21,7 +21,7 @@
 
 /**
  * Abstract Loader that provides an {@link AsyncTask} to do the work.
- * 
+ *
  * @param <D> the data type to be loaded.
  */
 public abstract class AsyncTaskLoader<D> extends Loader<D> {
@@ -35,15 +35,48 @@
         /* Runs on the UI thread */
         @Override
         protected void onPostExecute(D data) {
-            AsyncTaskLoader.this.onLoadComplete(data);
+            AsyncTaskLoader.this.dispatchOnLoadComplete(data);
         }
     }
 
+    private LoadListTask mTask;
+
     public AsyncTaskLoader(Context context) {
         super(context);
     }
 
     /**
+     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
+     * loaded data set and load a new one.
+     */
+    @Override
+    public void forceLoad() {
+        mTask = new LoadListTask();
+        mTask.execute((Void[]) null);
+    }
+
+    /**
+     * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
+     * for more info.
+     *
+     * @return <tt>false</tt> if the task could not be cancelled,
+     *         typically because it has already completed normally, or
+     *         because {@link startLoading()} hasn't been called, and
+     *         <tt>true</tt> otherwise
+     */
+    public boolean cancelLoad() {
+        if (mTask != null) {
+            return mTask.cancel(false);
+        }
+        return false;
+    }
+
+    private void dispatchOnLoadComplete(D data) {
+        mTask = null;
+        onLoadComplete(data);
+    }
+
+    /**
      * Called on a worker thread to perform the actual load. Implementions should not deliver the
      * results directly, but should return them from this this method and deliver them from
      * {@link #onPostExecute()}
diff --git a/src/android/app/patterns/CursorLoader.java b/src/android/app/patterns/CursorLoader.java
index a1f8132..e7b2e41 100644
--- a/src/android/app/patterns/CursorLoader.java
+++ b/src/android/app/patterns/CursorLoader.java
@@ -5,7 +5,7 @@
  * 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
+ *      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,
@@ -69,7 +69,7 @@
     /**
      * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
      * will be called on the UI thread. If a previous load has been completed and is still valid
-     * the result may be passed to the callbacks immediately. 
+     * the result may be passed to the callbacks immediately.
      *
      * Must be called from the UI thread
      */
@@ -85,15 +85,6 @@
     }
 
     /**
-     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
-     * loaded data set and load a new one.
-     */
-    @Override
-    public void forceLoad() {
-        new LoadListTask().execute((Void[]) null);
-    }
-
-    /**
      * Must be called from the UI thread
      */
     @Override
@@ -103,6 +94,9 @@
             mCursor = null;
         }
 
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+
         // Make sure that any outstanding loads clean themselves up properly
         mStopped = true;
     }
@@ -112,4 +106,36 @@
         // Ensure the loader is stopped
         stopLoading();
     }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    public void setUri(Uri uri) {
+        mUri = uri;
+    }
+
+    public String[] getProjection() {
+        return mProjection;
+    }
+
+    public void setProjection(String[] projection) {
+        mProjection = projection;
+    }
+
+    public String getSelection() {
+        return mSelection;
+    }
+
+    public void setSelection(String selection) {
+        mSelection = selection;
+    }
+
+    public String[] getSelectionArgs() {
+        return mSelectionArgs;
+    }
+
+    public void setSelectionArgs(String[] selectionArgs) {
+        mSelectionArgs = selectionArgs;
+    }
 }
diff --git a/src/android/app/patterns/Loader.java b/src/android/app/patterns/Loader.java
index dfc35e9..18c64f3 100644
--- a/src/android/app/patterns/Loader.java
+++ b/src/android/app/patterns/Loader.java
@@ -48,7 +48,7 @@
          * @param loader the loader that completed the load
          * @param data the result of the load
          */
-        public void onLoadComplete(Loader loader, D data);
+        public void onLoadComplete(Loader<D> loader, D data);
     }
 
     /**
@@ -89,7 +89,7 @@
     /**
      * Registers a class that will receive callbacks when a load is complete. The callbacks will
      * be called on the UI thread so it's safe to pass the results to widgets.
-     * 
+     *
      * Must be called from the UI thread
      */
     public void registerListener(int id, OnLoadCompleteListener<D> listener) {
@@ -118,7 +118,7 @@
      * will be called on the UI thread. If a previous load has been completed and is still valid
      * the result may be passed to the callbacks immediately. The loader will monitor the source of
      * the data set and may deliver future callbacks if the source changes. Calling
-     * {@link #stopLoading} will stop the delivery of callbacks. 
+     * {@link #stopLoading} will stop the delivery of callbacks.
      *
      * Must be called from the UI thread
      */
diff --git a/src/android/app/patterns/LoaderActivity.java b/src/android/app/patterns/LoaderActivity.java
index 664a8bc..bcb3692 100644
--- a/src/android/app/patterns/LoaderActivity.java
+++ b/src/android/app/patterns/LoaderActivity.java
@@ -30,12 +30,12 @@
         Loader.OnLoadCompleteListener<D> {
     private boolean mStarted = false;
 
-    static final class LoaderInfo {
+    static final class LoaderInfo<D> {
         public Bundle args;
-        public Loader loader;
+        public Loader<D> loader;
     }
-    private HashMap<Integer, LoaderInfo> mLoaders;
-    private HashMap<Integer, LoaderInfo> mInactiveLoaders;
+    private HashMap<Integer, LoaderInfo<D>> mLoaders;
+    private HashMap<Integer, LoaderInfo<D>> mInactiveLoaders;
 
     /**
      * Registers a loader with this activity, registers the callbacks on it, and starts it loading.
@@ -44,16 +44,16 @@
      * is destroyed.
      */
     protected void startLoading(int id, Bundle args) {
-        LoaderInfo info = mLoaders.get(id);
+        LoaderInfo<D> 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 = new LoaderInfo<D>();
         info.args = args;
         mLoaders.put(id, info);
-        Loader loader = onCreateLoader(id, args);
+        Loader<D> loader = onCreateLoader(id, args);
         info.loader = loader;
         if (mStarted) {
             // The activity will start all existing loaders in it's onStart(), so only start them
@@ -63,20 +63,20 @@
         }
     }
 
-    protected abstract Loader onCreateLoader(int id, Bundle args);
+    protected abstract Loader<D> onCreateLoader(int id, Bundle args);
     protected abstract void onInitializeLoaders();
-    protected abstract void onLoadFinished(Loader loader, D data);
+    protected abstract void onLoadFinished(Loader<D> loader, D data);
 
-    public final void onLoadComplete(Loader loader, D data) {
+    public final void onLoadComplete(Loader<D> loader, D data) {
         // Notify of the new data so the app can switch out the old data before
         // we try to destroy it.
         onLoadFinished(loader, data);
 
         // Look for an inactive loader and destroy it if found
         int id = loader.getId();
-        LoaderInfo info = mInactiveLoaders.get(id);
+        LoaderInfo<D> info = mInactiveLoaders.get(id);
         if (info != null) {
-            Loader oldLoader = info.loader;
+            Loader<D> oldLoader = info.loader;
             if (oldLoader != null) {
                 oldLoader.destroy();
             }
@@ -90,14 +90,14 @@
 
         if (mLoaders == null) {
             // Look for a passed along loader and create a new one if it's not there
-            mLoaders = (HashMap<Integer, LoaderInfo>) getLastNonConfigurationInstance();
+            mLoaders = (HashMap<Integer, LoaderInfo<D>>) getLastNonConfigurationInstance();
             if (mLoaders == null) {
-                mLoaders = new HashMap<Integer, LoaderInfo>();
+                mLoaders = new HashMap<Integer, LoaderInfo<D>>();
                 onInitializeLoaders();
             }
         }
         if (mInactiveLoaders == null) {
-            mInactiveLoaders = new HashMap<Integer, LoaderInfo>();
+            mInactiveLoaders = new HashMap<Integer, LoaderInfo<D>>();
         }
     }
 
@@ -107,9 +107,9 @@
 
         // 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 (HashMap.Entry<Integer, LoaderInfo> entry : mLoaders.entrySet()) {
-            LoaderInfo info = entry.getValue();
-            Loader loader = info.loader;
+        for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+            LoaderInfo<D> info = entry.getValue();
+            Loader<D> loader = info.loader;
             int id = entry.getKey();
             if (loader == null) {
                loader = onCreateLoader(id, info.args);
@@ -126,9 +126,9 @@
     public void onStop() {
         super.onStop();
 
-        for (HashMap.Entry<Integer, LoaderInfo> entry : mLoaders.entrySet()) {
-            LoaderInfo info = entry.getValue();
-            Loader loader = info.loader;
+        for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+            LoaderInfo<D> info = entry.getValue();
+            Loader<D> loader = info.loader;
             if (loader == null) {
                 continue;
             }
@@ -158,9 +158,9 @@
         super.onDestroy();
 
         if (mLoaders != null) {
-            for (HashMap.Entry<Integer, LoaderInfo> entry : mLoaders.entrySet()) {
-                LoaderInfo info = entry.getValue();
-                Loader loader = info.loader;
+            for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+                LoaderInfo<D> info = entry.getValue();
+                Loader<D> loader = info.loader;
                 if (loader == null) {
                     continue;
                 }
diff --git a/src/android/app/patterns/LoaderManagingFragment.java b/src/android/app/patterns/LoaderManagingFragment.java
new file mode 100644
index 0000000..32e3bf3
--- /dev/null
+++ b/src/android/app/patterns/LoaderManagingFragment.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.patterns;
+
+import android.app.Fragment;
+import android.os.Bundle;
+
+import java.util.HashMap;
+
+public abstract class LoaderManagingFragment<D> extends Fragment
+        implements Loader.OnLoadCompleteListener<D> {
+    private boolean mStarted = false;
+
+    static final class LoaderInfo<D> {
+        public Bundle args;
+        public Loader<D> loader;
+    }
+    private HashMap<Integer, LoaderInfo<D>> mLoaders;
+    private HashMap<Integer, LoaderInfo<D>> mInactiveLoaders;
+
+    /**
+     * Registers a loader with this activity, 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 it's work. The callback will be delivered before the old loader
+     * is destroyed.
+     */
+    protected void startLoading(int id, Bundle args) {
+        LoaderInfo<D> 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<D>();
+        info.args = args;
+        mLoaders.put(id, info);
+        Loader<D> loader = onCreateLoader(id, args);
+        info.loader = 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, this);
+            loader.startLoading();
+        }
+    }
+
+    protected abstract Loader<D> onCreateLoader(int id, Bundle args);
+    protected abstract void onInitializeLoaders();
+    protected abstract void onLoadFinished(Loader<D> loader, D data);
+
+    public final void onLoadComplete(Loader<D> loader, D data) {
+        // Notify of the new data so the app can switch out the old data before
+        // we try to destroy it.
+        onLoadFinished(loader, data);
+
+        // Look for an inactive loader and destroy it if found
+        int id = loader.getId();
+        LoaderInfo<D> info = mInactiveLoaders.get(id);
+        if (info != null) {
+            Loader<D> oldLoader = info.loader;
+            if (oldLoader != null) {
+                oldLoader.destroy();
+            }
+            mInactiveLoaders.remove(id);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        if (mLoaders == null) {
+            // Look for a passed along loader and create a new one if it's not there
+// TODO: uncomment once getLastNonConfigurationInstance method is available
+//            mLoaders = (HashMap<Integer, LoaderInfo>) getLastNonConfigurationInstance();
+            if (mLoaders == null) {
+                mLoaders = new HashMap<Integer, LoaderInfo<D>>();
+                onInitializeLoaders();
+            }
+        }
+        if (mInactiveLoaders == null) {
+            mInactiveLoaders = new HashMap<Integer, LoaderInfo<D>>();
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        // 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 (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+            LoaderInfo<D> info = entry.getValue();
+            Loader<D> loader = info.loader;
+            int id = entry.getKey();
+            if (loader == null) {
+               loader = onCreateLoader(id, info.args);
+               info.loader = loader;
+            }
+            loader.registerListener(id, this);
+            loader.startLoading();
+        }
+
+        mStarted = true;
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+            LoaderInfo<D> info = entry.getValue();
+            Loader<D> loader = info.loader;
+            if (loader == null) {
+                continue;
+            }
+
+            // Let the loader know we're done with it
+            loader.unregisterListener(this);
+
+            // The loader isn't getting passed along to the next instance so ask it to stop loading
+// TODO: uncomment once isChangingConfig method is available
+//            if (!getActivity().isChangingConfigurations()) {
+//                loader.stopLoading();
+//            }
+        }
+
+        mStarted = false;
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        // Pass the loader along to the next guy
+        Object result = mLoaders;
+        mLoaders = null;
+        return result;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        if (mLoaders != null) {
+            for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+                LoaderInfo<D> info = entry.getValue();
+                Loader<D> loader = info.loader;
+                if (loader == null) {
+                    continue;
+                }
+                loader.destroy();
+            }
+        }
+    }
+}