Update to new MVC framework

Change-Id: I792037f910de24b30f5b33e1b3f789c69a854ff5
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 5dabdc7..bc2d39d 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -19,11 +19,10 @@
 import com.android.contacts.ContactsSearchManager;
 import com.android.contacts.R;
 import com.android.contacts.mvcframework.DialogManager;
+import com.android.contacts.mvcframework.LoaderActivity;
 import com.android.contacts.views.detail.ContactDetailView;
 import com.android.contacts.views.detail.ContactLoader;
-import com.android.contacts.views.detail.ContactLoader.Result;
 
-import android.app.Activity;
 import android.app.Dialog;
 import android.net.Uri;
 import android.os.Bundle;
@@ -32,11 +31,10 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
-public class ContactDetailActivity extends Activity implements ContactLoader.Callbacks,
+public class ContactDetailActivity extends LoaderActivity<ContactLoader.Result> implements
         DialogManager.DialogShowingViewActivity {
+    private static final int LOADER_DETAILS = 1;
     private ContactDetailView mDetails;
-    private ContactLoader mLoader;
-    private Uri mUri;
     private DialogManager mDialogManager;
 
     private static final String TAG = "ContactDetailActivity";
@@ -54,66 +52,39 @@
 
         mDetails = (ContactDetailView) findViewById(R.id.contact_details);
         mDetails.setCallbacks(new ContactDetailView.DefaultCallbacks(this));
-
-        mUri = getIntent().getData();
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
+    public void onInitializeLoaders() {
+        startLoading(LOADER_DETAILS, null);
+    }
 
-        if (mLoader == null) {
-            // Look for a passed along loader and create a new one if it's not there
-            mLoader = (ContactLoader) getLastNonConfigurationInstance();
-            if (mLoader == null) {
-                mLoader = new ContactLoader(this, mUri);
+    @Override
+    protected ContactLoader onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            case LOADER_DETAILS: {
+                return new ContactLoader(this, getIntent().getData());
             }
         }
-        mLoader.registerCallbacks(this);
-        mLoader.startLoading();
+        return null;
     }
 
-    @Override
-    public void onStop() {
-        super.onStop();
-
-        // Let the loader know we're done with it
-        mLoader.unregisterCallbacks(this);
-
-        // The loader isn't getting passed along to the next instance so ask it to stop loading
-        // TODO: Readd this once we have framework support
-        /*if (!isChangingConfigurations()) {
-            mLoader.stopLoading();
-        }*/
-    }
 
     @Override
-    public Object onRetainNonConfigurationInstance() {
-        // Pass the loader along to the next guy
-        Object result = mLoader;
-        mLoader = null;
-        return result;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        if (mLoader != null) {
-            mLoader.destroy();
+    public void onLoadComplete(int id, ContactLoader.Result data) {
+        switch (id) {
+            case LOADER_DETAILS:
+                if (data == ContactLoader.Result.NOT_FOUND) {
+                    // Item has been deleted
+                    Log.i(TAG, "No contact found. Closing activity");
+                    finish();
+                    return;
+                }
+                mDetails.setData(data);
+                break;
         }
     }
 
-    public void onContactLoaded(Result contact) {
-        if (contact == ContactLoader.Result.NOT_FOUND) {
-            // Item has been deleted
-            Log.i(TAG, "No contact found. Closing activity");
-            finish();
-            return;
-        }
-        mDetails.setData(contact);
-    }
-
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // TODO: This is too hardwired.
diff --git a/src/com/android/contacts/mvcframework/CursorLoader.java b/src/com/android/contacts/mvcframework/CursorLoader.java
index 89ae997..25585be 100644
--- a/src/com/android/contacts/mvcframework/CursorLoader.java
+++ b/src/com/android/contacts/mvcframework/CursorLoader.java
@@ -20,19 +20,10 @@
 import android.database.Cursor;
 import android.os.AsyncTask;
 
-public abstract class CursorLoader extends Loader<CursorLoader.Callbacks> {
-    private Context mContext;
-    private Cursor mCursor;
-    private ForceLoadContentObserver mObserver;
-    private boolean mClosed;
-
-    public interface Callbacks {
-        public void onCursorLoaded(Cursor cursor);
-    }
-
-    protected Context getContext() {
-        return mContext;
-    }
+public abstract class CursorLoader extends Loader<Cursor> {
+    Cursor mCursor;
+    ForceLoadContentObserver mObserver;
+    boolean mClosed;
 
     final class LoadListTask extends AsyncTask<Void, Void, Cursor> {
         /* Runs on a worker thread */
@@ -56,15 +47,12 @@
                 return;
             }
             mCursor = cursor;
-            if (mCallbacks != null) {
-                // A listener is register, notify them of the result
-                mCallbacks.onCursorLoaded(cursor);
-            }
+            deliverResult(cursor);
         }
     }
 
     public CursorLoader(Context context) {
-        mContext = context.getApplicationContext();
+        super(context);
         mObserver = new ForceLoadContentObserver();
     }
 
@@ -78,7 +66,7 @@
     @Override
     public void startLoading() {
         if (mCursor != null) {
-            mCallbacks.onCursorLoaded(mCursor);
+            deliverResult(mCursor);
         } else {
             forceLoad();
         }
diff --git a/src/com/android/contacts/mvcframework/Loader.java b/src/com/android/contacts/mvcframework/Loader.java
index 456b53a..fab2f46 100644
--- a/src/com/android/contacts/mvcframework/Loader.java
+++ b/src/com/android/contacts/mvcframework/Loader.java
@@ -16,11 +16,14 @@
 
 package com.android.contacts.mvcframework;
 
+import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
 
-public abstract class Loader<E> {
-    protected E mCallbacks;
+public abstract class Loader<D> {
+    private int mId;
+    private OnLoadCompleteListener<D> mListener;
+    private Context mContext;
 
     protected final class ForceLoadContentObserver extends ContentObserver {
         public ForceLoadContentObserver() {
@@ -38,30 +41,53 @@
         }
     }
 
+    public interface OnLoadCompleteListener<D> {
+        public void onLoadComplete(int id, D data);
+    }
+
+    protected void deliverResult(D data) {
+        if (mListener != null) {
+            mListener.onLoadComplete(mId, data);
+
+        }
+    }
+
+    public Loader(Context context) {
+        mContext = context.getApplicationContext();
+    }
+
+    /**
+     * @return an application context
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
     /**
      * 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 registerCallbacks(E callbacks) {
-        if (mCallbacks != null) {
-            throw new IllegalStateException("There are already callbacks registered");
-        }
-        mCallbacks = callbacks;
+    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 unregisterCallbacks(E callbacks) {
-        if (mCallbacks == null) {
-            throw new IllegalStateException("No callbacks register");
+    public void unregisterListener(OnLoadCompleteListener<D> listener) {
+        if (mListener == null) {
+            throw new IllegalStateException("No listener register");
         }
-        if (mCallbacks != callbacks) {
-            throw new IllegalArgumentException("Attempting to unregister the wrong callbacks");
+        if (mListener != listener) {
+            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
         }
-        mCallbacks = null;
+        mListener = null;
     }
 
     /**
diff --git a/src/com/android/contacts/mvcframework/LoaderActivity.java b/src/com/android/contacts/mvcframework/LoaderActivity.java
new file mode 100644
index 0000000..0e74d62
--- /dev/null
+++ b/src/com/android/contacts/mvcframework/LoaderActivity.java
@@ -0,0 +1,144 @@
+/*
+ * 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 com.android.contacts.mvcframework;
+
+
+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> {
+    static final class LoaderInfo {
+        public Bundle args;
+        public Loader loader;
+    }
+    private HashMap<Integer, LoaderInfo> mLoaders;
+
+    /**
+     * Registers a loader with this activity, registers the callbacks on it, and starts it loading.
+     */
+    protected void startLoading(int id, Bundle args) {
+        LoaderInfo info = mLoaders.get(id);
+        Loader loader;
+        if (info != null) {
+            loader = info.loader;
+            if (loader != null) {
+                loader.unregisterListener(this);
+                loader.destroy();
+                info.loader = null;
+            }
+        } else {
+            info = new LoaderInfo();
+            info.args = args;
+        }
+        mLoaders.put(id, info);
+        loader = onCreateLoader(id, args);
+        loader.registerListener(id, this);
+        loader.startLoading();
+        info.loader = loader;
+    }
+
+    protected abstract Loader onCreateLoader(int id, Bundle args);
+    protected abstract void onInitializeLoaders();
+
+    public abstract void onLoadComplete(int id, D data);
+
+    @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();
+            }
+        }
+    }
+
+    @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;
+            } else {
+                loader.registerListener(id, this);
+            }
+            loader.startLoading();
+        }
+    }
+
+    @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();
+//            }
+        }
+    }
+
+    @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();
+            }
+        }
+    }
+}
diff --git a/src/com/android/contacts/views/detail/ContactLoader.java b/src/com/android/contacts/views/detail/ContactLoader.java
index 38e7a5d..4528387 100644
--- a/src/com/android/contacts/views/detail/ContactLoader.java
+++ b/src/com/android/contacts/views/detail/ContactLoader.java
@@ -42,8 +42,7 @@
 /**
  * Loads a single Contact and all it constituent RawContacts.
  */
-public class ContactLoader extends Loader<ContactLoader.Callbacks> {
-    private Context mContext;
+public class ContactLoader extends Loader<ContactLoader.Result> {
     private Uri mLookupUri;
     private Result mContact;
     private ForceLoadContentObserver mObserver;
@@ -141,7 +140,7 @@
     final class LoadContactTask extends AsyncTask<Void, Void, Result> {
         @Override
         protected Result doInBackground(Void... args) {
-            final ContentResolver resolver = mContext.getContentResolver();
+            final ContentResolver resolver = getContext().getContentResolver();
             Result result = loadContactHeaderData(resolver, mLookupUri);
             if (result == Result.NOT_FOUND) {
                 // No record found. Try to lookup up a new record with the same lookupKey.
@@ -312,21 +311,22 @@
                     mObserver = new ForceLoadContentObserver();
                 }
                 Log.i(TAG, "Registering content observer for " + mLookupUri);
-                mContext.getContentResolver().registerContentObserver(mLookupUri, true, mObserver);
-                mCallbacks.onContactLoaded(result);
+                getContext().getContentResolver().registerContentObserver(
+                        mLookupUri, true, mObserver);
+                deliverResult(result);
             }
         }
     }
 
     public ContactLoader(Context context, Uri lookupUri) {
-        mContext = context.getApplicationContext();
+        super(context);
         mLookupUri = lookupUri;
     }
 
     @Override
     public void startLoading() {
         if (mContact != null) {
-            mCallbacks.onContactLoaded(mContact);
+            deliverResult(mContact);
         } else {
             forceLoad();
         }
@@ -341,7 +341,7 @@
     public void stopLoading() {
         mContact = null;
         if (mObserver != null) {
-            mContext.getContentResolver().unregisterContentObserver(mObserver);
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
         }
     }