More work on modal field editors

Bug:2680726
Change-Id: I6685fd6d20358bc2f83b2b81294106e86db0fcee
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 62543d0..307b6cc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -397,7 +397,7 @@
             </intent-filter>
         </activity>
 
-        <!-- Edit (or insert) details for a contact -->
+        <!-- Edit and insert details for a contact -->
         <activity
             android:name=".activities.ContactEditorActivity"
             android:theme="@style/TallTitleBarTheme">
@@ -438,6 +438,7 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.EDIT" />
+                <action android:name="android.intent.action.INSERT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.cursor.item/email_v2" android:host="com.android.contacts" />
             </intent-filter>
@@ -449,6 +450,11 @@
             android:name=".util.EmptyService"
             android:exported="false" />
 
+        <!-- Service to save a contact -->
+        <service
+            android:name=".views.ContactSaveService"
+            android:exported="false" />
+
         <!-- Views the details of a single contact -->
         <activity android:name="ContactOptionsActivity"
             android:label="@string/contactOptionsTitle"
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index e325eba..1007410 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -87,7 +87,7 @@
             finish();
         }
 
-        public void onItemClicked(Intent intent) {
+        public void onEditorRequested(Intent intent) {
             startActivity(intent);
         }
 
diff --git a/src/com/android/contacts/activities/ContactFieldEditorActivity.java b/src/com/android/contacts/activities/ContactFieldEditorActivity.java
index 3358ee3..6f958f6 100644
--- a/src/com/android/contacts/activities/ContactFieldEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactFieldEditorActivity.java
@@ -50,9 +50,9 @@
         } else throw new IllegalArgumentException("Action is neither EDIT nor INSERT");
 
         if (isInsert) {
-            mFragment.setupUris(rawContactUri, null);
+            mFragment.setupInsert(intent.getType(), rawContactUri);
         } else {
-            mFragment.setupUris(rawContactUri, intent.getData());
+            mFragment.setupEdit(rawContactUri, intent.getData());
         }
     }
 
diff --git a/src/com/android/contacts/views/ContactSaveService.java b/src/com/android/contacts/views/ContactSaveService.java
new file mode 100644
index 0000000..936b8a4
--- /dev/null
+++ b/src/com/android/contacts/views/ContactSaveService.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.android.contacts.views;
+
+import android.app.IntentService;
+import android.content.ContentProviderOperation;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class ContactSaveService extends IntentService {
+    private static final String TAG = "ContactSaveService";
+
+    public static final String EXTRA_OPERATIONS = "Operations";
+
+    public ContactSaveService() {
+        super(TAG);
+        setIntentRedelivery(true);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        final Parcelable[] operationsArray = intent.getParcelableArrayExtra(EXTRA_OPERATIONS);
+
+        // We have to cast each item individually here
+        final ArrayList<ContentProviderOperation> operations =
+                new ArrayList<ContentProviderOperation>(operationsArray.length);
+        for (Parcelable p : operationsArray) {
+            operations.add((ContentProviderOperation) p);
+        }
+
+        try {
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error saving", e);
+        } catch (OperationApplicationException e) {
+            Log.e(TAG, "Error saving", e);
+        }
+    }
+}
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index 493a351..28a1854 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -18,7 +18,6 @@
 
 import com.android.contacts.ContactOptionsActivity;
 import com.android.contacts.R;
-import com.android.contacts.TypePrecedence;
 import com.android.contacts.activities.ContactFieldEditorActivity;
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
@@ -83,7 +82,6 @@
 import android.widget.AdapterView.OnItemClickListener;
 
 import java.util.ArrayList;
-import java.util.zip.Inflater;
 
 public class ContactEditorFragment extends LoaderManagingFragment<ContactLoader.Result>
         implements OnCreateContextMenuListener, OnItemClickListener {
@@ -824,7 +822,7 @@
             final DataViewEntry entry = (DataViewEntry) baseEntry;
             final Intent intent = entry.intent;
             if (intent == null) return;
-            mListener.onItemClicked(intent);
+            mListener.onEditorRequested(intent);
         }
     }
 
@@ -881,22 +879,35 @@
                 if (rawContact == null) return null;
                 final ContactsSource source = rawContact.getSource();
 
-                final ArrayList<DataKind> sortedDataKinds = source.getSortedDataKinds();
-                final ArrayList<String> items = new ArrayList<String>(sortedDataKinds.size());
-                for (DataKind dataKind : sortedDataKinds) {
+                final ArrayList<DataKind> originalDataKinds = source.getSortedDataKinds();
+                // We should not modify the result returned from getSortedDataKinds but
+                // we have to filter some items out. Therefore we copy items into a new ArrayList
+                final ArrayList<DataKind> filteredDataKinds =
+                        new ArrayList<DataKind>(originalDataKinds.size());
+                final ArrayList<String> items = new ArrayList<String>(filteredDataKinds.size());
+                for (DataKind dataKind : originalDataKinds) {
                     // TODO: Filter out fields that do not make sense in the current Context
                     //       (Name, Photo, Notes etc)
                     if (dataKind.titleRes == -1) continue;
                     if (!dataKind.editable) continue;
                     final String title = mContext.getString(dataKind.titleRes);
                     items.add(title);
+                    filteredDataKinds.add(dataKind);
                 }
                 final DialogInterface.OnClickListener itemClickListener =
                         new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int which) {
-                        // TODO: Launch Intent to show Dialog
-//                        final KindSectionView view = (KindSectionView) mFields.getChildAt(which);
-//                        view.addItem();
+                        // Create an Intent for the INSERT-Editor. Its data is null
+                        // and the RawContact is identified in the Extras
+                        final String rawContactUriString = ContentUris.withAppendedId(
+                                RawContacts.CONTENT_URI, rawContactId).toString();
+                        final DataKind dataKind = filteredDataKinds.get(which);
+                        final Intent intent = new Intent();
+                        intent.setType(dataKind.mimeType);
+                        intent.setAction(Intent.ACTION_INSERT);
+                        intent.putExtra(ContactFieldEditorActivity.BUNDLE_RAW_CONTACT_URI,
+                                rawContactUriString);
+                        if (mListener != null) mListener.onEditorRequested(intent);
                     }
                 };
                 return new AlertDialog.Builder(mContext)
@@ -1029,9 +1040,9 @@
         public void onError();
 
         /**
-         * User clicked a single item (e.g. mail)
+         * User clicked a single item (e.g. mail) to edit it or is adding a new field
          */
-        public void onItemClicked(Intent intent);
+        public void onEditorRequested(Intent intent);
 
         /**
          * Show a dialog using the globally unique id
diff --git a/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java b/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java
index 11b9481..295b984 100644
--- a/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java
+++ b/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java
@@ -18,6 +18,8 @@
 
 import com.android.contacts.R;
 import com.android.contacts.views.ContactLoader;
+import com.android.contacts.views.ContactSaveService;
+import com.android.internal.util.ArrayUtils;
 
 import android.app.Activity;
 import android.app.LoaderManagingFragment;
@@ -27,10 +29,12 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Entity;
+import android.content.Intent;
 import android.content.Loader;
 import android.content.OperationApplicationException;
 import android.content.ContentProviderOperation.Builder;
 import android.content.Entity.NamedContentValues;
+import android.content.pm.PackageParser.ServiceIntentInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -48,6 +52,7 @@
 
     private static final int LOADER_DETAILS = 1;
 
+    private String mMimeType;
     private long mRawContactId;
     private Uri mRawContactUri;
     private long mDataId;
@@ -67,14 +72,26 @@
     }
 
     /**
-     * Sets the Uris for the Fragment. If the dataUri is null, this is interpreted as an Insert
-     * to an existing rawContact. rawContactUri must always be given
+     * Sets up the Fragment for Insert-Mode. Neither mimeType nor rawContactUri must be null
      */
-    public void setupUris(Uri rawContactUri, Uri dataUri) {
+    public void setupInsert(String mimeType, Uri rawContactUri) {
+        mMimeType = mimeType;
+        mRawContactUri = rawContactUri;
+        mRawContactId = Long.parseLong(rawContactUri.getLastPathSegment());
+        mDataUri = null;
+        mDataId = 0;
+    }
+
+    /**
+     * Sets up the Fragment for Edit-Mode. Neither rawContactUri nor dataUri must be null
+     * and dataUri must reference a data item that is associated with the given rawContactUri
+     */
+    public void setupEdit(Uri rawContactUri, Uri dataUri) {
+        mMimeType = null;
         mRawContactUri = rawContactUri;
         mRawContactId = Long.parseLong(rawContactUri.getLastPathSegment());
         mDataUri = dataUri;
-        mDataId = dataUri == null ? 0 : Long.parseLong(dataUri.getLastPathSegment());
+        mDataId = Long.parseLong(dataUri.getLastPathSegment());
     }
 
     @Override
@@ -114,28 +131,30 @@
                 mContactData = data;
 
                 // Find the correct RawContact
-                boolean valueFound = false;
                 for (Entity entity : mContactData.getEntities()) {
-                    final ContentValues entValues = entity.getEntityValues();
-                    final long rawContactId = entValues.getAsLong(RawContacts._ID);
+                    final ContentValues rawContactEntity = entity.getEntityValues();
+                    final long rawContactId = rawContactEntity.getAsLong(RawContacts._ID);
                     if (rawContactId == mRawContactId) {
+                        if (mDataId == 0) {
+                            // Do an INSERT
+                            setupEmpty(rawContactEntity);
+                            return;
+                        }
+                        // Do an EDIT. Find the correct item
                         for (NamedContentValues subValue : entity.getSubValues()) {
                             final long dataId = subValue.values.getAsLong(Data._ID);
                             if (dataId == mDataId) {
                                 loadData(subValue);
-                                valueFound = true;
-                                break;
+                                return;
                             }
                         }
                     }
                 }
-                if (!valueFound) {
-                    // Item has been deleted
-                    Log.i(TAG, "Data item not found. Closing activity");
-                    if (mListener != null) mListener.onDataNotFound();
-                    return;
-                }
-                break;
+
+                // Item could not be found
+                Log.i(TAG, "Data item not found. Closing activity");
+                if (mListener != null) mListener.onDataNotFound();
+                return;
             default: {
                 Log.wtf(TAG, "Unknown ID in onLoadFinished: " + id);
             }
@@ -152,6 +171,7 @@
         if (getDataUri() == null) {
             // INSERT
             builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+            builder.withValue(Data.MIMETYPE, mMimeType);
             builder.withValue(Data.RAW_CONTACT_ID, getRawContactId());
             saveData(builder);
         } else {
@@ -161,18 +181,18 @@
         }
         operations.add(builder.build());
 
-        // TODO: Do in Background thread/service
-        try {
-            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
-        } catch (RemoteException e) {
-            Toast.makeText(getActivity(), R.string.edit_error_saving, Toast.LENGTH_LONG).show();
-        } catch (OperationApplicationException e) {
-            Toast.makeText(getActivity(), R.string.edit_error_saving, Toast.LENGTH_LONG).show();
-        }
+        // Tell the Service to save
+        final Intent serviceIntent = new Intent();
+        final ContentProviderOperation[] operationsArray =
+                operations.toArray(ArrayUtils.emptyArray(ContentProviderOperation.class));
+        serviceIntent.putExtra(ContactSaveService.EXTRA_OPERATIONS, operationsArray);
+        serviceIntent.setClass(getActivity().getApplicationContext(), ContactSaveService.class);
+
+        getActivity().startService(serviceIntent);
     }
 
     protected abstract void saveData(final Builder builder);
-
+    protected abstract void setupEmpty(ContentValues rawContactEntity);
     protected abstract void loadData(NamedContentValues contentValues);
 
     public void setListener(Listener listener) {
diff --git a/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java b/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java
index d838ea8..7760b64 100644
--- a/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java
+++ b/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java
@@ -19,6 +19,7 @@
 import com.android.contacts.R;
 import com.android.contacts.util.ViewGroupAnimator;
 
+import android.content.ContentValues;
 import android.content.ContentProviderOperation.Builder;
 import android.content.Entity.NamedContentValues;
 import android.graphics.Color;
@@ -111,6 +112,11 @@
     };
 
     @Override
+    protected void setupEmpty(ContentValues rawContactEntity) {
+        pushTypeButton(Email.TYPE_HOME, false);
+    }
+
+    @Override
     protected void loadData(NamedContentValues contentValues) {
         mEditText.setText(contentValues.values.getAsString(Email.ADDRESS));
         pushTypeButton(contentValues.values.getAsInteger(Email.TYPE).intValue(), false);