Merge "Re-use the Detail-Loader in the Editor"
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntitySet.java
index 83fe338..0f4d68d 100644
--- a/src/com/android/contacts/model/EntitySet.java
+++ b/src/com/android/contacts/model/EntitySet.java
@@ -33,6 +33,7 @@
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 
 /**
  * Container for multiple {@link EntityDelta} objects, usually when editing
@@ -62,25 +63,32 @@
      */
     public static EntitySet fromQuery(ContentResolver resolver, String selection,
             String[] selectionArgs, String sortOrder) {
-        EntityIterator iterator = RawContacts.newEntityIterator(resolver.query(
+        final EntityIterator iterator = RawContacts.newEntityIterator(resolver.query(
                 RawContactsEntity.CONTENT_URI, null, selection, selectionArgs,
                 sortOrder));
         try {
-            final EntitySet state = new EntitySet();
-            // Perform background query to pull contact details
-            while (iterator.hasNext()) {
-                // Read all contacts into local deltas to prepare for edits
-                final Entity before = iterator.next();
-                final EntityDelta entity = EntityDelta.fromBefore(before);
-                state.add(entity);
-            }
-            return state;
+            return fromIterator(iterator);
         } finally {
             iterator.close();
         }
     }
 
     /**
+     * Create an {@link EntitySet} that contains the entities of the Iterator as before values.
+     */
+    public static EntitySet fromIterator(Iterator<Entity> iterator) {
+        final EntitySet state = new EntitySet();
+        // Perform background query to pull contact details
+        while (iterator.hasNext()) {
+            // Read all contacts into local deltas to prepare for edits
+            final Entity before = iterator.next();
+            final EntityDelta entity = EntityDelta.fromBefore(before);
+            state.add(entity);
+        }
+        return state;
+    }
+
+    /**
      * Merge the "after" values from the given {@link EntitySet}, discarding any
      * previous "after" states. This is typically used when re-parenting user
      * edits onto an updated {@link EntitySet}.
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index e50994a..5b281fc 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -33,6 +33,7 @@
 import com.android.contacts.ui.widget.PhotoEditorView;
 import com.android.contacts.util.EmptyService;
 import com.android.contacts.util.WeakAsyncTask;
+import com.android.contacts.views.ContactLoader;
 
 import android.accounts.Account;
 import android.app.Activity;
@@ -98,7 +99,7 @@
 //TODO Cleanup the load function. It can currenlty also do insert, which is awkward
 //TODO Watch for background changes...How?
 
-public class ContactEditorFragment extends LoaderManagingFragment<ContactEditorLoader.Result> {
+public class ContactEditorFragment extends LoaderManagingFragment<ContactLoader.Result> {
 
     private static final String TAG = "ContactEditorFragment";
 
@@ -244,14 +245,14 @@
     }
 
     @Override
-    protected Loader<ContactEditorLoader.Result> onCreateLoader(int id, Bundle args) {
-        return new ContactEditorLoader(mContext, mUri, mMimeType, mIntentExtras);
+    protected Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+        return new ContactLoader(mContext, mUri);
     }
 
     @Override
-    protected void onLoadFinished(Loader<ContactEditorLoader.Result> loader,
-            ContactEditorLoader.Result data) {
-        if (data == ContactEditorLoader.Result.NOT_FOUND) {
+    protected void onLoadFinished(Loader<ContactLoader.Result> loader,
+            ContactLoader.Result data) {
+        if (data == ContactLoader.Result.NOT_FOUND) {
             // Item has been deleted
             Log.i(TAG, "No contact found. Closing fragment");
             if (mListener != null) mListener.closeBecauseContactNotFound();
@@ -260,8 +261,22 @@
         setData(data);
     }
 
-    public void setData(ContactEditorLoader.Result data) {
-        mState = data.getEntitySet();
+    public void setData(ContactLoader.Result data) {
+        mState = EntitySet.fromIterator(data.getEntities().iterator());
+        // TODO: Merge in Intent parameters can only be done on the first load.
+        // The behaviour for subsequent loads is probably broken, so fix this
+        final boolean hasExtras = mIntentExtras != null && mIntentExtras.size() > 0;
+        final boolean hasState = mState.size() > 0;
+        if (hasExtras && hasState) {
+            // Find source defining the first RawContact found
+            // TODO: Test this. Can we actually always use the first RawContact. This seems wrong
+            final EntityDelta state = mState.get(0);
+            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+            final Sources sources = Sources.getInstance(mContext);
+            final ContactsSource source = sources.getInflatedSource(accountType,
+                    ContactsSource.LEVEL_CONSTRAINTS);
+            EntityModifier.parseExtras(mContext, source, state, mIntentExtras);
+        }
         bindEditors();
     }
 
diff --git a/src/com/android/contacts/views/editor/ContactEditorLoader.java b/src/com/android/contacts/views/editor/ContactEditorLoader.java
deleted file mode 100644
index 77c03c0..0000000
--- a/src/com/android/contacts/views/editor/ContactEditorLoader.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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.views.editor;
-
-import com.android.contacts.ContactsUtils;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.EntitySet;
-import com.android.contacts.model.Sources;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Loader;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-
-public class ContactEditorLoader extends Loader<ContactEditorLoader.Result> {
-    private static final String TAG = "ContactEditorLoader";
-
-    private final Uri mLookupUri;
-    private final String mMimeType;
-    private Result mContact;
-    private boolean mDestroyed;
-    private ForceLoadContentObserver mObserver;
-    private final Bundle mIntentExtras;
-
-    public ContactEditorLoader(Context context, Uri lookupUri, String mimeType,
-            Bundle intentExtras) {
-        super(context);
-        mLookupUri = lookupUri;
-        mMimeType = mimeType;
-        mIntentExtras = intentExtras;
-    }
-
-    /**
-     * The result of a load operation. Contains all data necessary to display the contact for
-     * editing.
-     */
-    public static class Result {
-        /**
-         * Singleton instance that represents "No Contact Found"
-         */
-        public static final Result NOT_FOUND = new Result(null);
-
-        private final EntitySet mEntitySet;
-
-        private Result(EntitySet entitySet) {
-            mEntitySet = entitySet;
-        }
-
-        public EntitySet getEntitySet() {
-            return mEntitySet;
-        }
-    }
-
-    private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
-        @Override
-        protected Result doInBackground(Void... params) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
-
-            // Handle both legacy and new authorities
-
-            final long contactId;
-            final String selection = "0";
-            if (Contacts.CONTENT_ITEM_TYPE.equals(mMimeType)) {
-                // Handle selected aggregate
-                contactId = ContentUris.parseId(uriCurrentFormat);
-            } else if (RawContacts.CONTENT_ITEM_TYPE.equals(mMimeType)) {
-                // Get id of corresponding aggregate
-                final long rawContactId = ContentUris.parseId(uriCurrentFormat);
-                contactId = ContactsUtils.queryForContactId(resolver, rawContactId);
-            } else throw new IllegalStateException();
-
-            return new Result(EntitySet.fromQuery(resolver, RawContacts.CONTACT_ID + "=?",
-                    new String[] { String.valueOf(contactId) }, null));
-        }
-
-        /**
-         * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
-         * For legacy contacts, a raw-contact lookup is performed.
-         */
-        private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
-            if (uri == null) throw new IllegalArgumentException("uri must not be null");
-
-            final String authority = uri.getAuthority();
-
-            // Current Style Uri?
-            if (ContactsContract.AUTHORITY.equals(authority)) {
-                final String type = resolver.getType(uri);
-                // Contact-Uri? Good, return it
-                if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
-                    return uri;
-                }
-
-                // RawContact-Uri? Transform it to ContactUri
-                if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
-                    final long rawContactId = ContentUris.parseId(uri);
-                    return RawContacts.getContactLookupUri(getContext().getContentResolver(),
-                            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-                }
-
-                // Anything else? We don't know what this is
-                throw new IllegalArgumentException("uri format is unknown");
-            }
-
-            // Legacy Style? Convert to RawContact
-            final String OBSOLETE_AUTHORITY = "contacts";
-            if (OBSOLETE_AUTHORITY.equals(authority)) {
-                // Legacy Format. Convert to RawContact-Uri and then lookup the contact
-                final long rawContactId = ContentUris.parseId(uri);
-                return RawContacts.getContactLookupUri(resolver,
-                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-            }
-
-            throw new IllegalArgumentException("uri authority is unknown");
-        }
-
-        @Override
-        protected void onPostExecute(Result result) {
-            super.onPostExecute(result);
-
-            // TODO: This merging of extras is probably wrong on subsequent loads
-
-            // Load edit details in background
-            final Sources sources = Sources.getInstance(getContext());
-
-            // Handle any incoming values that should be inserted
-            final boolean hasExtras = mIntentExtras != null && mIntentExtras.size() > 0;
-            final boolean hasState = result.getEntitySet().size() > 0;
-            if (hasExtras && hasState) {
-                // Find source defining the first RawContact found
-                final EntityDelta state = result.getEntitySet().get(0);
-                final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-                final ContactsSource source = sources.getInflatedSource(accountType,
-                        ContactsSource.LEVEL_CONSTRAINTS);
-                EntityModifier.parseExtras(getContext(), source, state, mIntentExtras);
-            }
-
-            // The creator isn't interested in any further updates
-            if (mDestroyed) {
-                return;
-            }
-
-            mContact = result;
-            if (result != null) {
-                if (mObserver == null) {
-                    mObserver = new ForceLoadContentObserver();
-                }
-                // TODO: Do we want a content observer here?
-//                Log.i(TAG, "Registering content observer for " + mLookupUri);
-//                getContext().getContentResolver().registerContentObserver(mLookupUri, true,
-//                        mObserver);
-                deliverResult(result);
-            }
-        }
-    }
-
-    @Override
-    public void startLoading() {
-        if (mContact != null) {
-            deliverResult(mContact);
-        } else {
-            forceLoad();
-        }
-    }
-
-    @Override
-    public void forceLoad() {
-        LoadContactTask task = new LoadContactTask();
-        task.execute((Void[])null);
-    }
-
-    @Override
-    public void stopLoading() {
-        mContact = null;
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-        }
-    }
-
-    @Override
-    public void destroy() {
-        mContact = null;
-        mDestroyed = true;
-    }
-}
\ No newline at end of file