Adding "Create new group" functionality

Change-Id: I2d0c28fcf3e9b3099c8889560a149f18f0f74c38
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1374ba9..52b28df 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1407,6 +1407,12 @@
     Initiates a contact import dialog [CHAR LIMIT=128] -->
     <string name="contacts_unavailable_import_contacts">Import contacts from a file</string>
 
+    <!-- Title of the dialog that allows creation of a contact group [CHAR LIMIT=128] -->
+    <string name="create_group_dialog_title">Create new group</string>
+
+    <!-- An item in the popup list of groups that triggers creation of a contact group [CHAR LIMIT=128] -->
+    <string name="create_group_item_label">[Create new group]</string>
+
     <!-- Title of the dialog that allows renaming of a contact group [CHAR LIMIT=128] -->
     <string name="rename_group_dialog_title">Rename group</string>
 
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 0b6213f..2b22b4b 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -28,7 +28,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -78,6 +78,15 @@
     }
 
     @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        if (Intent.ACTION_EDIT.equals(intent.getAction()) && mFragment != null){
+            mFragment.setIntentExtras(intent.getExtras());
+        }
+    }
+
+    @Override
     protected Dialog onCreateDialog(int id, Bundle args) {
         if (DialogManager.isManagedId(id)) return mDialogManager.onCreateDialog(id, args);
 
@@ -168,7 +177,7 @@
 
             // Pass on all the data that has been entered so far
             if (values != null && values.size() != 0) {
-                intent.putParcelableArrayListExtra(Insert.DATA, values);
+                intent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, values);
             }
 
             startActivity(intent);
diff --git a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
new file mode 100644
index 0000000..9f1ad15
--- /dev/null
+++ b/src/com/android/contacts/interactions/GroupCreationDialogFragment.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 com.android.contacts.interactions;
+
+import com.android.contacts.R;
+import com.android.contacts.views.ContactSaveService;
+
+import android.accounts.Account;
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.widget.EditText;
+
+/**
+ * A dialog for creating a new group.
+ */
+public class GroupCreationDialogFragment extends GroupNameDialogFragment {
+    private static final String ARG_ACCOUNT_TYPE = "accountType";
+    private static final String ARG_ACCOUNT_NAME = "accountName";
+
+    public static void show(
+            FragmentManager fragmentManager, String accountType, String accountName) {
+        GroupCreationDialogFragment dialog = new GroupCreationDialogFragment();
+        Bundle args = new Bundle();
+        args.putString(ARG_ACCOUNT_TYPE, accountType);
+        args.putString(ARG_ACCOUNT_NAME, accountName);
+        dialog.setArguments(args);
+        dialog.show(fragmentManager, "createGroup");
+    }
+
+    @Override
+    protected void initializeGroupLabelEditText(EditText editText) {
+    }
+
+    @Override
+    protected int getTitleResourceId() {
+        return R.string.create_group_dialog_title;
+    }
+
+    @Override
+    protected void onCompleted(String groupLabel) {
+        Bundle arguments = getArguments();
+        String accountType = arguments.getString(ARG_ACCOUNT_TYPE);
+        String accountName = arguments.getString(ARG_ACCOUNT_NAME);
+
+        getActivity().startService(ContactSaveService.createNewGroupIntent(
+                getActivity(), new Account(accountName, accountType), groupLabel));
+    }
+}
diff --git a/src/com/android/contacts/interactions/GroupNameDialogFragment.java b/src/com/android/contacts/interactions/GroupNameDialogFragment.java
new file mode 100644
index 0000000..d390385
--- /dev/null
+++ b/src/com/android/contacts/interactions/GroupNameDialogFragment.java
@@ -0,0 +1,92 @@
+/*
+ * 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.interactions;
+
+import com.android.contacts.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnShowListener;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * A common superclass for creating and renaming groups.
+ */
+public abstract class GroupNameDialogFragment extends DialogFragment
+        implements TextWatcher, OnShowListener {
+    private EditText mEdit;
+
+    protected abstract int getTitleResourceId();
+    protected abstract void initializeGroupLabelEditText(EditText editText);
+    protected abstract void onCompleted(String groupLabel);
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        View view = LayoutInflater.from(getActivity()).inflate(R.layout.group_name_dialog, null);
+        mEdit = (EditText) view.findViewById(R.id.group_label);
+        mEdit.addTextChangedListener(this);
+
+        AlertDialog dialog = new AlertDialog.Builder(getActivity())
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setTitle(getTitleResourceId())
+                .setView(view)
+                .setPositiveButton(android.R.string.ok,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int whichButton) {
+                            onCompleted(mEdit.getText().toString().trim());
+                        }
+                    }
+                )
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+
+        dialog.setOnShowListener(this);
+        return dialog;
+    }
+
+    public void onShow(DialogInterface dialog) {
+        updateOkButtonState((AlertDialog) dialog);
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        AlertDialog dialog = (AlertDialog) getDialog();
+        updateOkButtonState(dialog);
+    }
+
+    private void updateOkButtonState(AlertDialog dialog) {
+        Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+        okButton.setEnabled(!TextUtils.isEmpty(mEdit.getText().toString().trim()));
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+}
diff --git a/src/com/android/contacts/interactions/GroupRenamingDialogFragment.java b/src/com/android/contacts/interactions/GroupRenamingDialogFragment.java
index 6339e79..223391e 100644
--- a/src/com/android/contacts/interactions/GroupRenamingDialogFragment.java
+++ b/src/com/android/contacts/interactions/GroupRenamingDialogFragment.java
@@ -18,26 +18,18 @@
 import com.android.contacts.R;
 import com.android.contacts.views.ContactSaveService;
 
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
 import android.app.FragmentManager;
-import android.content.DialogInterface;
 import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
 import android.widget.EditText;
 
 /**
  * A dialog for renaming a group.
  */
-public class GroupRenamingDialogFragment extends DialogFragment {
+public class GroupRenamingDialogFragment extends GroupNameDialogFragment {
 
     private static final String ARG_GROUP_ID = "groupId";
     private static final String ARG_LABEL = "label";
 
-    private EditText mEdit;
-
     public static void show(FragmentManager fragmentManager, long groupId, String label) {
         GroupRenamingDialogFragment dialog = new GroupRenamingDialogFragment();
         Bundle args = new Bundle();
@@ -48,38 +40,25 @@
     }
 
     @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        View view = LayoutInflater.from(getActivity()).inflate(R.layout.group_name_dialog, null);
-        mEdit = (EditText) view.findViewById(R.id.group_label);
+    protected void initializeGroupLabelEditText(EditText editText) {
         String label = getArguments().getString(ARG_LABEL);
-        mEdit.setText(label);
+        editText.setText(label);
         if (label != null) {
-            mEdit.setSelection(label.length());
+            editText.setSelection(label.length());
         }
-
-        return new AlertDialog.Builder(getActivity())
-                .setIcon(android.R.drawable.ic_dialog_alert)
-                .setTitle(R.string.rename_group_dialog_title)
-                .setView(view)
-                .setPositiveButton(android.R.string.ok,
-                    new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int whichButton) {
-                            saveGroupLabel();
-                        }
-                    }
-                )
-                .setNegativeButton(android.R.string.cancel, null)
-                .create();
     }
 
-    protected void saveGroupLabel() {
-        String newLabel = mEdit.getText().toString();
+    @Override
+    protected int getTitleResourceId() {
+        return R.string.rename_group_dialog_title;
+    }
 
+    @Override
+    protected void onCompleted(String groupLabel) {
         Bundle arguments = getArguments();
         long groupId = arguments.getLong(ARG_GROUP_ID);
 
         getActivity().startService(ContactSaveService.createGroupRenameIntent(
-                getActivity(), groupId, newLabel));
+                getActivity(), groupId, groupLabel));
     }
 }
diff --git a/src/com/android/contacts/views/ContactSaveService.java b/src/com/android/contacts/views/ContactSaveService.java
index 04ab43a..4083a4e 100644
--- a/src/com/android/contacts/views/ContactSaveService.java
+++ b/src/com/android/contacts/views/ContactSaveService.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.views;
 
+import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
 import android.accounts.Account;
@@ -32,6 +33,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.RawContacts;
@@ -53,6 +55,7 @@
 
     public static final String EXTRA_OPERATIONS = "Operations";
 
+    public static final String ACTION_CREATE_GROUP = "createGroup";
     public static final String ACTION_RENAME_GROUP = "renameGroup";
     public static final String ACTION_DELETE_GROUP = "deleteGroup";
     public static final String EXTRA_GROUP_ID = "groupId";
@@ -88,6 +91,8 @@
         String action = intent.getAction();
         if (ACTION_NEW_RAW_CONTACT.equals(action)) {
             createRawContact(intent);
+        } else if (ACTION_CREATE_GROUP.equals(action)) {
+            createGroup(intent);
         } else if (ACTION_RENAME_GROUP.equals(action)) {
             renameGroup(intent);
         } else if (ACTION_DELETE_GROUP.equals(action)) {
@@ -127,7 +132,6 @@
             throw new RuntimeException("Failed to store new contact", e);
         }
 
-        Uri result = ContactsContract.Directory.CONTENT_URI;
         Uri rawContactUri = results[0].uri;
         callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
 
@@ -180,6 +184,52 @@
         return serviceIntent;
     }
 
+    private void createGroup(Intent intent) {
+        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
+        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
+        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+
+        ContentValues values = new ContentValues();
+        values.put(Groups.ACCOUNT_TYPE, accountType);
+        values.put(Groups.ACCOUNT_NAME, accountName);
+        values.put(Groups.TITLE, label);
+
+        Uri groupUri = getContentResolver().insert(Groups.CONTENT_URI, values);
+        if (groupUri == null) {
+            return;
+        }
+
+        values.clear();
+        values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+        values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
+
+        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+        callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
+
+        startActivity(callbackIntent);
+    }
+
+    /**
+     * Creates an intent that can be sent to this service to create a new group.
+     */
+    public static Intent createNewGroupIntent(Activity activity, Account account, String label) {
+        Intent serviceIntent = new Intent(activity, ContactSaveService.class);
+        serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
+        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
+        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
+        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
+
+        // Callback intent will be invoked by the service once the new group is
+        // created.  The service will put a group membership row in the extras
+        // of the callback intent.
+        Intent callbackIntent = new Intent(activity, activity.getClass());
+        callbackIntent.setAction(Intent.ACTION_EDIT);
+        callbackIntent.setFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
+        return serviceIntent;
+    }
+
     private void renameGroup(Intent intent) {
         long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
         String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index a2679b5..df329f9 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -366,26 +366,33 @@
         sb.append(")");
         mQuerySelection = sb.toString();
         mState = EntityDeltaList.fromIterator(entities.iterator());
-
-        // Merge in Extras from Intent
-        if (mIntentExtras != null && mIntentExtras.size() > 0) {
-            final AccountTypes sources = AccountTypes.getInstance(mContext);
-            for (EntityDelta state : mState) {
-                final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-                final AccountType source = sources.getInflatedSource(accountType,
-                        AccountType.LEVEL_CONSTRAINTS);
-                if (!source.readOnly) {
-                    // Apply extras to the first writable raw contact only
-                    EntityModifier.parseExtras(mContext, source, state, mIntentExtras);
-                    mIntentExtras = null;
-                    break;
-                }
-            }
-        }
+        setIntentExtras(mIntentExtras);
+        mIntentExtras = null;
 
         bindEditors();
     }
 
+    /**
+     * Merges extras from the intent.
+     */
+    public void setIntentExtras(Bundle extras) {
+        if (extras == null || extras.size() == 0) {
+            return;
+        }
+
+        final AccountTypes sources = AccountTypes.getInstance(mContext);
+        for (EntityDelta state : mState) {
+            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+            final AccountType source = sources.getInflatedSource(accountType,
+                    AccountType.LEVEL_CONSTRAINTS);
+            if (!source.readOnly) {
+                // Apply extras to the first writable raw contact only
+                EntityModifier.parseExtras(mContext, source, state, extras);
+                break;
+            }
+        }
+    }
+
     private void selectAccountAndCreateContact(boolean isNewContact) {
         final ArrayList<Account> accounts = AccountTypes.getInstance(mContext).getAccounts(true);
         // No Accounts available.  Create a phone-local contact.
diff --git a/src/com/android/contacts/views/editor/GroupMembershipView.java b/src/com/android/contacts/views/editor/GroupMembershipView.java
index 60d1801..5705561 100644
--- a/src/com/android/contacts/views/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/views/editor/GroupMembershipView.java
@@ -17,12 +17,14 @@
 package com.android.contacts.views.editor;
 
 import com.android.contacts.R;
+import com.android.contacts.interactions.GroupCreationDialogFragment;
 import com.android.contacts.model.AccountType.DataKind;
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.model.EntityModifier;
 import com.android.contacts.views.GroupMetaDataLoader;
 
+import android.app.Activity;
 import android.content.Context;
 import android.database.Cursor;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
@@ -47,6 +49,8 @@
 public class GroupMembershipView extends LinearLayout
         implements OnClickListener, OnItemClickListener {
 
+    private static final int CREATE_NEW_GROUP_GROUP_ID = 133;
+
     public static final class GroupSelectionItem {
         private final long mGroupId;
         private final String mTitle;
@@ -203,6 +207,9 @@
             }
         }
 
+        mAdapter.add(new GroupSelectionItem(CREATE_NEW_GROUP_GROUP_ID,
+                getContext().getString(R.string.create_group_item_label), false));
+
         mPopup = new ListPopupWindow(getContext(), null);
         mPopup.setAnchorView(mGroupList);
         mPopup.setAdapter(mAdapter);
@@ -224,6 +231,7 @@
         super.onDetachedFromWindow();
         if (mPopup != null) {
             mPopup.dismiss();
+            mPopup = null;
         }
     }
 
@@ -231,6 +239,13 @@
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         ListView list = (ListView) parent;
         int count = mAdapter.getCount();
+
+        if (list.isItemChecked(count - 1)) {
+            list.setItemChecked(count - 1, false);
+            createNewGroup();
+            return;
+        }
+
         for (int i = 0; i < count; i++) {
             mAdapter.getItem(i).setChecked(list.isItemChecked(i));
         }
@@ -292,4 +307,14 @@
         }
         return false;
     }
+
+    private void createNewGroup() {
+        if (mPopup != null) {
+            mPopup.dismiss();
+            mPopup = null;
+        }
+
+        GroupCreationDialogFragment.show(
+                ((Activity) getContext()).getFragmentManager(), mAccountType, mAccountName);
+    }
 }