Refactor to coupler/presenter pattern. Moved framework classes to correct packages

Bug:2579760
Change-Id: Ia0c475e55830aa2d8b13cdb2685fc0981b6f6ade
diff --git a/src/android/app/patterns/AsyncTaskLoader.java b/src/android/app/patterns/AsyncTaskLoader.java
new file mode 100644
index 0000000..01b3e24
--- /dev/null
+++ b/src/android/app/patterns/AsyncTaskLoader.java
@@ -0,0 +1,61 @@
+/*
+ * 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.patterns;
+
+import android.content.Context;
+import android.os.AsyncTask;
+
+/**
+ * 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> {
+    final class LoadListTask extends AsyncTask<Void, Void, D> {
+        /* Runs on a worker thread */
+        @Override
+        protected D doInBackground(Void... params) {
+            return AsyncTaskLoader.this.loadInBackground();
+        }
+
+        /* Runs on the UI thread */
+        @Override
+        protected void onPostExecute(D data) {
+            AsyncTaskLoader.this.onLoadComplete(data);
+        }
+    }
+
+    public AsyncTaskLoader(Context context) {
+        super(context);
+    }
+
+    /**
+     * 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()}
+     *
+     * @return the result of the load
+     */
+    protected abstract D loadInBackground();
+
+    /**
+     * Called on the UI thread with the result of the load.
+     *
+     * @param data the result of the load
+     */
+    protected abstract void onLoadComplete(D data);
+}
diff --git a/src/android/app/patterns/CursorLoader.java b/src/android/app/patterns/CursorLoader.java
new file mode 100644
index 0000000..a1f8132
--- /dev/null
+++ b/src/android/app/patterns/CursorLoader.java
@@ -0,0 +1,115 @@
+/*
+ * 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.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class CursorLoader extends AsyncTaskLoader<Cursor> {
+    Cursor mCursor;
+    ForceLoadContentObserver mObserver;
+    boolean mStopped;
+    Uri mUri;
+    String[] mProjection;
+    String mSelection;
+    String[] mSelectionArgs;
+    String mSortOrder;
+
+    /* Runs on a worker thread */
+    @Override
+    protected Cursor loadInBackground() {
+        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
+                mSelectionArgs, mSortOrder);
+        // Ensure the cursor window is filled
+        if (cursor != null) {
+            cursor.getCount();
+            cursor.registerContentObserver(mObserver);
+        }
+        return cursor;
+    }
+
+    /* Runs on the UI thread */
+    @Override
+    protected void onLoadComplete(Cursor cursor) {
+        if (mStopped) {
+            // An async query came in while the loader is stopped
+            cursor.close();
+            return;
+        }
+        mCursor = cursor;
+        deliverResult(cursor);
+    }
+
+    public CursorLoader(Context context, Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        super(context);
+        mObserver = new ForceLoadContentObserver();
+        mUri = uri;
+        mProjection = projection;
+        mSelection = selection;
+        mSelectionArgs = selectionArgs;
+        mSortOrder = sortOrder;
+    }
+
+    /**
+     * 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. 
+     *
+     * Must be called from the UI thread
+     */
+    @Override
+    public void startLoading() {
+        mStopped = false;
+
+        if (mCursor != null) {
+            deliverResult(mCursor);
+        } else {
+            forceLoad();
+        }
+    }
+
+    /**
+     * 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
+    public void stopLoading() {
+        if (mCursor != null && !mCursor.isClosed()) {
+            mCursor.close();
+            mCursor = null;
+        }
+
+        // Make sure that any outstanding loads clean themselves up properly
+        mStopped = true;
+    }
+
+    @Override
+    public void destroy() {
+        // Ensure the loader is stopped
+        stopLoading();
+    }
+}
diff --git a/src/android/app/patterns/Loader.java b/src/android/app/patterns/Loader.java
new file mode 100644
index 0000000..dfc35e9
--- /dev/null
+++ b/src/android/app/patterns/Loader.java
@@ -0,0 +1,146 @@
+/*
+ * 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.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+
+public abstract class Loader<D> {
+    private int mId;
+    private OnLoadCompleteListener<D> mListener;
+    private Context mContext;
+
+    protected final class ForceLoadContentObserver extends ContentObserver {
+        public ForceLoadContentObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            forceLoad();
+        }
+    }
+
+    public interface OnLoadCompleteListener<D> {
+        /**
+         * Called on the thread that created the Loader when the load is complete.
+         *
+         * @param loader the loader that completed the load
+         * @param data the result of the load
+         */
+        public void onLoadComplete(Loader loader, D data);
+    }
+
+    /**
+     * Sends the result of the load to the register listener.
+     *
+     * @param data the result of the load
+     */
+    protected void deliverResult(D data) {
+        if (mListener != null) {
+            mListener.onLoadComplete(this, data);
+        }
+    }
+
+    /**
+     * Stores away the application context associated with context. Since Loaders can be used
+     * across multiple activities it's dangerous to store the context directly.
+     *
+     * @param context used to retrieve the application context.
+     */
+    public Loader(Context context) {
+        mContext = context.getApplicationContext();
+    }
+
+    /**
+     * @return an application context retrieved from the Context passed to the constructor.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * @return the ID of this loader
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * 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) {
+        if (mListener != null) {
+            throw new IllegalStateException("There is already a listener registered");
+        }
+        mListener = listener;
+        mId = id;
+    }
+
+    /**
+     * Must be called from the UI thread
+     */
+    public void unregisterListener(OnLoadCompleteListener<D> listener) {
+        if (mListener == null) {
+            throw new IllegalStateException("No listener register");
+        }
+        if (mListener != listener) {
+            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
+        }
+        mListener = null;
+    }
+
+    /**
+     * 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 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. 
+     *
+     * Must be called from the UI thread
+     */
+    public abstract void startLoading();
+
+    /**
+     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
+     * loaded data set and load a new one.
+     */
+    public abstract void forceLoad();
+
+    /**
+     * Stops delivery of updates until the next time {@link #startLoading()} is called
+     *
+     * Must be called from the UI thread
+     */
+    public abstract void stopLoading();
+
+    /**
+     * Destroys the loader and frees it's resources, making it unusable.
+     *
+     * Must be called from the UI thread
+     */
+    public abstract void destroy();
+}
\ No newline at end of file
diff --git a/src/android/app/patterns/LoaderActivity.java b/src/android/app/patterns/LoaderActivity.java
new file mode 100644
index 0000000..664a8bc
--- /dev/null
+++ b/src/android/app/patterns/LoaderActivity.java
@@ -0,0 +1,171 @@
+/*
+ * 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.Activity;
+import android.os.Bundle;
+
+import java.util.HashMap;
+
+/**
+ * The idea here was to abstract the generic life cycle junk needed to properly keep loaders going.
+ * It didn't work out as-is because registering the callbacks post config change didn't work.
+ */
+public abstract class LoaderActivity<D> extends Activity implements
+        Loader.OnLoadCompleteListener<D> {
+    private boolean mStarted = false;
+
+    static final class LoaderInfo {
+        public Bundle args;
+        public Loader loader;
+    }
+    private HashMap<Integer, LoaderInfo> mLoaders;
+    private HashMap<Integer, LoaderInfo> 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 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;
+        mLoaders.put(id, info);
+        Loader 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 onCreateLoader(int id, Bundle args);
+    protected abstract void onInitializeLoaders();
+    protected abstract void onLoadFinished(Loader loader, D data);
+
+    public final void onLoadComplete(Loader 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);
+        if (info != null) {
+            Loader 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
+            mLoaders = (HashMap<Integer, LoaderInfo>) getLastNonConfigurationInstance();
+            if (mLoaders == null) {
+                mLoaders = new HashMap<Integer, LoaderInfo>();
+                onInitializeLoaders();
+            }
+        }
+        if (mInactiveLoaders == null) {
+            mInactiveLoaders = new HashMap<Integer, LoaderInfo>();
+        }
+    }
+
+    @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> entry : mLoaders.entrySet()) {
+            LoaderInfo info = entry.getValue();
+            Loader 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> entry : mLoaders.entrySet()) {
+            LoaderInfo info = entry.getValue();
+            Loader 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
+//            if (!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> entry : mLoaders.entrySet()) {
+                LoaderInfo info = entry.getValue();
+                Loader loader = info.loader;
+                if (loader == null) {
+                    continue;
+                }
+                loader.destroy();
+            }
+        }
+    }
+}