Prevent user from creating duplicate labels.

This isn't enforced at the data layer just in the UI's that
allow editting of the group name.

Bug 28718604
Change-Id: I969b0298bd895fdbdd7f8609da05f2b801707c0a
(cherry picked from commit 8d62cd2c5c855fd5483c030c1d59e50830ce4dfc)
diff --git a/res/layout/floating_action_button.xml b/res/layout/floating_action_button.xml
index 95c76ae..2dc8955 100644
--- a/res/layout/floating_action_button.xml
+++ b/res/layout/floating_action_button.xml
@@ -14,24 +14,26 @@
      limitations under the License.
 -->
 
-<!-- This expects to be included inside a RelativeLayout -->
+<!-- This expects to be included inside a RelativeLayout or a CoordinatorLayout -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/floating_action_button_container"
     android:layout_width="@dimen/floating_action_button_width"
     android:layout_height="@dimen/floating_action_button_height"
-    android:layout_marginEnd="@dimen/floating_action_button_margin_right"
-    android:layout_marginBottom="@dimen/floating_action_button_margin_bottom"
-    android:background="@drawable/fab_blue"
+    android:layout_alignParentBottom="true"
     android:layout_alignParentEnd="true"
-    android:layout_alignParentBottom="true">
+    android:layout_gravity="bottom|end"
+    android:layout_marginBottom="@dimen/floating_action_button_margin_bottom"
+    android:layout_marginEnd="@dimen/floating_action_button_margin_right"
+    android:background="@drawable/fab_blue"
+    android:elevation="@dimen/design_fab_elevation">
 
     <ImageButton
         android:id="@+id/floating_action_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:background="@drawable/floating_action_button"
-        android:tint="@color/floating_action_button_icon_color"
         android:contentDescription="@string/action_menu_add_new_contact_button"
-        android:src="@drawable/ic_add"/>
+        android:src="@drawable/ic_add"
+        android:tint="@color/floating_action_button_icon_color"/>
 </FrameLayout>
\ No newline at end of file
diff --git a/res/layout/group_name_dialog.xml b/res/layout/group_name_dialog.xml
deleted file mode 100644
index f19df86..0000000
--- a/res/layout/group_name_dialog.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingLeft="25dip"
-    android:paddingRight="25dip"
-    android:paddingStart="25dip"
-    android:paddingEnd="25dip"
-    android:paddingTop="25dip"
-    android:paddingBottom="25dip">
-    <EditText
-        android:id="@+id/group_label"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="@dimen/group_name_edit_text_min_height"/>
-</FrameLayout>
diff --git a/res/layout/group_name_edit_dialog.xml b/res/layout/group_name_edit_dialog.xml
index 9a8c69f..5df8c9d 100644
--- a/res/layout/group_name_edit_dialog.xml
+++ b/res/layout/group_name_edit_dialog.xml
@@ -14,24 +14,28 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.design.widget.TextInputLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/text_input_layout"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:paddingBottom="24dp"
+    android:paddingEnd="24dp"
     android:paddingStart="24dp"
-    android:paddingEnd="24dp">
+    app:errorEnabled="true"
+    app:hintEnabled="false">
 
-    <EditText android:id="@android:id/text1"
-        xmlns:android="http://schemas.android.com/apk/res/android"
+    <android.support.design.widget.TextInputEditText
+        android:id="@android:id/text1"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="@dimen/group_name_edit_text_min_height"
         android:layout_marginBottom="4dp"
         android:layout_marginRight="4dp"
         android:layout_marginTop="16dp"
         android:hint="@string/group_name_dialog_hint"
         android:inputType="text"
-        android:singleLine="true"
-        android:maxLength="@integer/group_name_max_length"/>
-</LinearLayout>
\ No newline at end of file
+        android:maxLength="@integer/group_name_max_length"
+        android:minHeight="@dimen/group_name_edit_text_min_height"
+        android:singleLine="true"/>
+</android.support.design.widget.TextInputLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 909b684..0ccc19b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -369,6 +369,9 @@
     <!-- Toast displayed when saving a label failed [CHAR LIMIT=70] -->
     <string name="groupSavedErrorToast">Couldn\'t save label changes.</string>
 
+    <!-- Message displayed when creating a group with the same name as an existing group -->
+    <string name="groupExistsErrorMessage">That label already exists</string>
+
     <!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" is selected -->
     <plurals name="listTotalPhoneContacts">
         <item quantity="one">1 contact with phone number</item>
@@ -632,9 +635,6 @@
     Initiates a contact import dialog [CHAR LIMIT=128] -->
     <string name="contacts_unavailable_import_contacts">Import</string>
 
-    <!-- Title of the dialog that allows creation of a contact label [CHAR LIMIT=50] -->
-    <string name="create_group_dialog_title">Create new label</string>
-
     <!-- An item in the popup list of labels that triggers creation of a contact label [CHAR LIMIT=128] -->
     <string name="create_group_item_label">Create new&#8230;</string>
 
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index c8d8eb3..bea1411 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -88,8 +88,7 @@
         AccountFiltersListener,
         GroupsListener,
         NavigationView.OnNavigationItemSelectedListener,
-        SelectAccountDialogFragment.Listener,
-        GroupNameEditDialogFragment.Listener {
+        SelectAccountDialogFragment.Listener {
 
     protected static String TAG = "ContactsDrawerActivity";
 
@@ -650,26 +649,11 @@
     @Override
     public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
         mNewGroupAccount = account;
-        GroupNameEditDialogFragment.showInsertDialog(
-                getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
+        GroupNameEditDialogFragment.newInstanceForCreation(mNewGroupAccount, ACTION_CREATE_GROUP)
+                .show(getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
     }
 
     @Override
     public void onAccountSelectorCancelled() {
     }
-
-    @Override
-    public void onGroupNameEdit(String groupName, boolean isInsert) {
-        if (mNewGroupAccount == null) {
-            Toast.makeText(this, R.string.groupCreateFailedToast, Toast.LENGTH_SHORT).show();
-            return;
-        }
-        startService(ContactSaveService.createNewGroupIntent(this,
-                mNewGroupAccount, groupName, /* rawContactsToAdd */ null, getClass(),
-                ACTION_CREATE_GROUP));
-    }
-
-    @Override
-    public void onGroupNameEditCancelled() {
-    }
 }
diff --git a/src/com/android/contacts/activities/GroupMembersActivity.java b/src/com/android/contacts/activities/GroupMembersActivity.java
index 093c8b6..68f2f44 100644
--- a/src/com/android/contacts/activities/GroupMembersActivity.java
+++ b/src/com/android/contacts/activities/GroupMembersActivity.java
@@ -36,6 +36,7 @@
 import com.android.contacts.common.logging.ListEvent;
 import com.android.contacts.common.logging.Logger;
 import com.android.contacts.common.logging.ScreenEvent.ScreenType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.group.GroupMembersFragment;
 import com.android.contacts.group.GroupMetadata;
@@ -53,8 +54,7 @@
 public class GroupMembersActivity extends ContactsDrawerActivity implements
         ActionBarAdapter.Listener,
         MultiSelectContactsListFragment.OnCheckBoxListActionListener,
-        GroupMembersFragment.GroupMembersListener,
-        GroupNameEditDialogFragment.Listener {
+        GroupMembersFragment.GroupMembersListener {
 
     private static final String TAG = "GroupMembers";
 
@@ -382,8 +382,11 @@
                 return true;
             }
             case R.id.menu_rename_group: {
-                GroupNameEditDialogFragment.showUpdateDialog(
-                        getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG, mGroupMetadata.groupName);
+                GroupNameEditDialogFragment.newInstanceForUpdate(
+                        new AccountWithDataSet(mGroupMetadata.accountName,
+                                mGroupMetadata.accountType, mGroupMetadata.dataSet),
+                        ACTION_UPDATE_GROUP, mGroupMetadata.groupId, mGroupMetadata.groupName)
+                        .show(getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
                 return true;
             }
             case R.id.menu_delete_group: {
@@ -567,23 +570,6 @@
         mActionBarAdapter.setSelectionMode(false);
     }
 
-    // GroupNameEditDialogFragment.Listener callbacks
-
-    @Override
-    public void onGroupNameEdit(String groupName, boolean isInsert) {
-        if (isInsert) {
-            super.onGroupNameEdit(groupName, isInsert);
-            return;
-        }
-        startService(ContactSaveService.createGroupRenameIntent(this,
-                mGroupMetadata.groupId, groupName, GroupMembersActivity.class,
-                ACTION_UPDATE_GROUP));
-    }
-
-    @Override
-    public void onGroupNameEditCancelled() {
-    }
-
     // GroupMembersFragment callbacks
 
     @Override
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index d41991c..ba0c2c6 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -38,9 +38,9 @@
 
 import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
+import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.model.dataitem.DataKind;
-import com.android.contacts.interactions.GroupCreationDialogFragment;
-import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener;
+import com.android.contacts.group.GroupNameEditDialogFragment;
 import com.android.contacts.common.model.RawContactDelta;
 import com.android.contacts.common.model.ValuesDelta;
 import com.android.contacts.common.model.RawContactModifier;
@@ -56,6 +56,8 @@
 public class GroupMembershipView extends LinearLayout
         implements OnClickListener, OnItemClickListener {
 
+    public static final String TAG_CREATE_GROUP_FRAGMENT = "createGroupDialog";
+
     private static final int CREATE_NEW_GROUP_GROUP_ID = 133;
 
     public static final class GroupSelectionItem {
@@ -449,17 +451,23 @@
         UiClosables.closeQuietly(mPopup);
         mPopup = null;
 
-        GroupCreationDialogFragment.show(
+        final GroupNameEditDialogFragment dialog =
+                GroupNameEditDialogFragment.newInstanceForCreation(
+                        new AccountWithDataSet(mAccountName, mAccountType, mDataSet), null);
+
+        // If the device is rotated after the dialog is shown, the listener will become null,
+        // so that the popup from GroupMembershipView will not be shown.
+        dialog.setListener(new GroupNameEditDialogFragment.Listener() {
+            @Override
+            public void onGroupNameEditStarted(String groupName) {
+                mCreatedNewGroup = true;
+            }
+            @Override
+            public void onGroupNameEditCancelled() { }
+        });
+        dialog.show(
                 ((Activity) getContext()).getFragmentManager(),
-                mAccountType,
-                mAccountName,
-                mDataSet,
-                new OnGroupCreatedListener() {
-                    @Override
-                    public void onGroupCreated() {
-                        mCreatedNewGroup = true;
-                    }
-                });
+                TAG_CREATE_GROUP_FRAGMENT);
     }
 
 }
diff --git a/src/com/android/contacts/group/GroupNameEditDialogFragment.java b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
index 235a878..b2bfd0b 100644
--- a/src/com/android/contacts/group/GroupNameEditDialogFragment.java
+++ b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
@@ -17,11 +17,18 @@
 
 import android.app.Dialog;
 import android.app.DialogFragment;
-import android.app.FragmentManager;
+import android.app.LoaderManager;
 import android.content.Context;
+import android.content.CursorLoader;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.support.design.widget.TextInputLayout;
 import android.support.v7.app.AlertDialog;
 import android.text.Editable;
 import android.text.TextUtils;
@@ -33,60 +40,99 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
+import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.google.common.base.Strings;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Edits the name of a group.
  */
-public final class GroupNameEditDialogFragment extends DialogFragment {
+public final class GroupNameEditDialogFragment extends DialogFragment implements
+        LoaderManager.LoaderCallbacks<Cursor> {
 
-    private static final String KEY_IS_INSERT = "isInsert";
     private static final String KEY_GROUP_NAME = "groupName";
 
     private static final String ARG_IS_INSERT = "isInsert";
     private static final String ARG_GROUP_NAME = "groupName";
+    private static final String ARG_ACCOUNT = "account";
+    private static final String ARG_CALLBACK_ACTION = "callbackAction";
+    private static final String ARG_GROUP_ID = "groupId";
+
+    private static final long NO_GROUP_ID = -1;
+
 
     /** Callbacks for hosts of the {@link GroupNameEditDialogFragment}. */
     public interface Listener {
-        void onGroupNameEdit(String groupName, boolean isInsert);
+        void onGroupNameEditStarted(String name);
         void onGroupNameEditCancelled();
+
+        public static final Listener None = new Listener() {
+            @Override
+            public void onGroupNameEditStarted(String name) { }
+
+            @Override
+            public void onGroupNameEditCancelled() { }
+        };
     }
 
     private boolean mIsInsert;
     private String mGroupName;
+    private long mGroupId;
+    private Listener mListener;
+    private AccountWithDataSet mAccount;
     private EditText mGroupNameEditText;
+    private TextInputLayout mGroupNameTextLayout;
+    private Set<String> mExistingGroups = Collections.emptySet();
 
-    public static void showInsertDialog(FragmentManager fragmentManager, String tag) {
-        showDialog(fragmentManager, tag, /* isInsert */ true, /* groupName */ null);
+    public static GroupNameEditDialogFragment newInstanceForCreation(
+            AccountWithDataSet account, String callbackAction) {
+        return newInstance(account, callbackAction, NO_GROUP_ID, null);
     }
 
-    public static void showUpdateDialog(FragmentManager fragmentManager,
-            String tag, String groupName) {
-        showDialog(fragmentManager, tag, /* isInsert */ false, groupName);
+    public static GroupNameEditDialogFragment newInstanceForUpdate(
+            AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
+        return newInstance(account, callbackAction, groupId, groupName);
     }
 
-    private static void showDialog(FragmentManager fragmentManager,
-            String tag, boolean isInsert, String groupName) {
+    private static GroupNameEditDialogFragment newInstance(
+            AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
+        if (account == null || account.name == null || account.type == null) {
+            throw new IllegalArgumentException("Invalid account");
+        }
+        final boolean isInsert = groupId == NO_GROUP_ID;
         final Bundle args = new Bundle();
         args.putBoolean(ARG_IS_INSERT, isInsert);
+        args.putLong(ARG_GROUP_ID, groupId);
         args.putString(ARG_GROUP_NAME, groupName);
+        args.putParcelable(ARG_ACCOUNT, account);
+        args.putString(ARG_CALLBACK_ACTION, callbackAction);
 
         final GroupNameEditDialogFragment dialog = new GroupNameEditDialogFragment();
         dialog.setArguments(args);
-        dialog.show(fragmentManager, tag);
+        return dialog;
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setStyle(STYLE_NORMAL, R.style.ContactsAlertDialogThemeAppCompat);
+        final Bundle args = getArguments();
         if (savedInstanceState == null) {
-            final Bundle args = getArguments();
-            mIsInsert = args.getBoolean(KEY_IS_INSERT);
             mGroupName = args.getString(KEY_GROUP_NAME);
         } else {
-            mIsInsert = savedInstanceState.getBoolean(ARG_IS_INSERT);
             mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
         }
+        mGroupId = args.getLong(ARG_GROUP_ID, NO_GROUP_ID);
+        mIsInsert = args.getBoolean(ARG_IS_INSERT, true);
+        mAccount = getArguments().getParcelable(ARG_ACCOUNT);
+
+        // There is only one loader so the id arg doesn't matter.
+        getLoaderManager().initLoader(0, null, this);
     }
 
     @Override
@@ -96,7 +142,7 @@
         title.setText(mIsInsert
                 ? R.string.group_name_dialog_insert_title
                 : R.string.group_name_dialog_update_title);
-        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), getTheme())
                 .setCustomTitle(title)
                 .setView(R.layout.group_name_edit_dialog)
                 .setNegativeButton(android.R.string.cancel, new OnClickListener() {
@@ -107,12 +153,9 @@
                         dismiss();
                     }
                 })
-                .setPositiveButton(android.R.string.ok, new OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        getListener().onGroupNameEdit(getGroupName(), mIsInsert);
-                    }
-                });
+                // The Positive button listener is defined below in the OnShowListener to
+                // allow for input validation
+                .setPositiveButton(android.R.string.ok, null);
 
         // Disable the create button when the name is empty
         final AlertDialog alertDialog = builder.create();
@@ -122,6 +165,8 @@
             @Override
             public void onShow(DialogInterface dialog) {
                 mGroupNameEditText = (EditText) alertDialog.findViewById(android.R.id.text1);
+                mGroupNameTextLayout =
+                        (TextInputLayout) alertDialog.findViewById(R.id.text_input_layout);
                 if (!TextUtils.isEmpty(mGroupName)) {
                     mGroupNameEditText.setText(mGroupName);
                     // Guard against already created group names that are longer than the max
@@ -134,6 +179,14 @@
 
                 final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
                 createButton.setEnabled(!TextUtils.isEmpty(getGroupName()));
+
+                // Override the click listener to prevent dismissal if creating a duplicate group.
+                createButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        maybePersistCurrentGroupName(v);
+                    }
+                });
                 mGroupNameEditText.addTextChangedListener(new TextWatcher() {
                     @Override
                     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -145,6 +198,7 @@
 
                     @Override
                     public void afterTextChanged(Editable s) {
+                        mGroupNameTextLayout.setError(null);
                         createButton.setEnabled(!TextUtils.isEmpty(s));
                     }
                 });
@@ -154,6 +208,56 @@
         return alertDialog;
     }
 
+    /**
+     * Sets the listener for the rename
+     *
+     * Setting a listener on a fragment is error prone since it will be lost if the fragment
+     * is recreated. This exists because it is used from a view class (GroupMembersView) which
+     * needs to modify it's state when this fragment updates the name.
+     *
+     * @param listener the listener. can be null
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    private boolean hasNameChanged() {
+        final String name = Strings.nullToEmpty(getGroupName());
+        final String originalName = getArguments().getString(ARG_GROUP_NAME);
+        return (mIsInsert && !name.isEmpty()) || !name.equals(originalName);
+    }
+
+    private void maybePersistCurrentGroupName(View button) {
+        if (!hasNameChanged()) {
+            dismiss();
+            return;
+        }
+        final String name = getGroupName();
+        // Note we don't check if the loader finished populating mExistingGroups. It's not the
+        // end of the world if the user ends up with a duplicate group and in practice it should
+        // never really happen (the query should complete much sooner than the user can edit the
+        // label)
+        if (mExistingGroups.contains(name)) {
+            mGroupNameTextLayout.setError(
+                    getString(R.string.groupExistsErrorMessage));
+            button.setEnabled(false);
+            return;
+        }
+        final String callbackAction = getArguments().getString(ARG_CALLBACK_ACTION);
+        final Intent serviceIntent;
+        if (mIsInsert) {
+            serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(),
+                    new AccountWithDataSet(mAccount.name, mAccount.type, mAccount.dataSet),
+                    name, null, getActivity().getClass(), callbackAction);
+        } else {
+            serviceIntent = ContactSaveService.createGroupRenameIntent(getActivity(), mGroupId,
+                    name, getActivity().getClass(), callbackAction);
+        }
+        ContactSaveService.startService(getActivity(), serviceIntent);
+        getListener().onGroupNameEditStarted(name);
+        dismiss();
+    }
+
     @Override
     public void onCancel(DialogInterface dialog) {
         super.onCancel(dialog);
@@ -163,10 +267,61 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putBoolean(KEY_IS_INSERT, mIsInsert);
         outState.putString(KEY_GROUP_NAME, getGroupName());
     }
 
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // Only a single loader so id is ignored.
+        return new CursorLoader(getActivity(), GroupNameQuery.URI,
+                GroupNameQuery.PROJECTION, GroupNameQuery.getSelection(mAccount),
+                GroupNameQuery.getSelectionArgs(mAccount), null);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        mExistingGroups = new HashSet<>();
+        while (data.moveToNext()) {
+            mExistingGroups.add(data.getString(GroupNameQuery.TITLE));
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    /**
+     * Defines the structure of the query performed by the CursorLoader created by
+     * GroupNameEditDialogFragment
+     */
+    private static class GroupNameQuery {
+
+        public static final int TITLE = 0;
+        public static final Uri URI = ContactsContract.Groups.CONTENT_URI;
+        public static final String[] PROJECTION = new String[] { ContactsContract.Groups.TITLE };
+
+        public static String getSelection(AccountWithDataSet account) {
+            final StringBuilder builder = new StringBuilder();
+            builder.append(ContactsContract.Groups.ACCOUNT_NAME).append("=? AND ")
+                    .append(ContactsContract.Groups.ACCOUNT_TYPE).append("=?");
+            if (account.dataSet != null) {
+                builder.append(" AND ").append(ContactsContract.Groups.DATA_SET).append("=?");
+            }
+            return builder.toString();
+        }
+
+        public static String[] getSelectionArgs(AccountWithDataSet account) {
+            final int len = account.dataSet == null ? 2 : 3;
+            final String[] args = new String[len];
+            args[0] = account.name;
+            args[1] = account.type;
+            if (account.dataSet != null) {
+                args[2] = account.dataSet;
+            }
+            return args;
+        }
+    }
+
     private void showInputMethod(View view) {
         final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
                 Context.INPUT_METHOD_SERVICE);
@@ -184,11 +339,13 @@
     }
 
     private Listener getListener() {
-        if (!(getActivity() instanceof Listener)) {
-            throw new ClassCastException(getActivity() + " must implement " +
-                    Listener.class.getName());
+        if (mListener != null) {
+            return mListener;
+        } else if (getActivity() instanceof Listener) {
+            return (Listener) getActivity();
+        } else {
+            return Listener.None;
         }
-        return (Listener) getActivity();
     }
 
     private String getGroupName() {
diff --git a/src/com/android/contacts/group/GroupsFragment.java b/src/com/android/contacts/group/GroupsFragment.java
index be1b44a..508c184 100644
--- a/src/com/android/contacts/group/GroupsFragment.java
+++ b/src/com/android/contacts/group/GroupsFragment.java
@@ -18,7 +18,6 @@
 
 import android.app.Fragment;
 import android.app.LoaderManager;
-import android.content.Context;
 import android.content.CursorLoader;
 import android.content.Loader;
 import android.database.Cursor;
diff --git a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
deleted file mode 100644
index e998688..0000000
--- a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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 android.app.Activity;
-import android.app.FragmentManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.EditText;
-
-import com.android.contacts.ContactSaveService;
-import com.android.contacts.R;
-import com.android.contacts.activities.ContactEditorBaseActivity;
-import com.android.contacts.common.model.account.AccountWithDataSet;
-
-/**
- * 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";
-    private static final String ARG_DATA_SET = "dataSet";
-
-    public static final String FRAGMENT_TAG = "createGroupDialog";
-
-    private final OnGroupCreatedListener mListener;
-
-    public interface OnGroupCreatedListener {
-        public void onGroupCreated();
-    }
-
-    public static void show(
-            FragmentManager fragmentManager, String accountType, String accountName,
-            String dataSet, OnGroupCreatedListener listener) {
-        GroupCreationDialogFragment dialog = new GroupCreationDialogFragment(listener);
-        Bundle args = new Bundle();
-        args.putString(ARG_ACCOUNT_TYPE, accountType);
-        args.putString(ARG_ACCOUNT_NAME, accountName);
-        args.putString(ARG_DATA_SET, dataSet);
-        dialog.setArguments(args);
-        dialog.show(fragmentManager, FRAGMENT_TAG);
-    }
-
-    public GroupCreationDialogFragment() {
-        super();
-        mListener = null;
-    }
-
-    private GroupCreationDialogFragment(OnGroupCreatedListener listener) {
-        super();
-        mListener = listener;
-    }
-
-    public OnGroupCreatedListener getOnGroupCreatedListener() {
-        return mListener;
-    }
-
-    @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);
-        String dataSet = arguments.getString(ARG_DATA_SET);
-
-        // Indicate to the listener that a new group will be created.
-        // If the device is rotated, mListener will become null, so that the
-        // popup from GroupMembershipView will not be shown.
-        if (mListener != null) {
-            mListener.onGroupCreated();
-        }
-
-        Activity activity = getActivity();
-        activity.startService(ContactSaveService.createNewGroupIntent(activity,
-                new AccountWithDataSet(accountName, accountType, dataSet), groupLabel,
-                null /* no new members to add */,
-                activity.getClass(), ContactEditorBaseActivity.ACTION_EDIT));
-    }
-}
diff --git a/src/com/android/contacts/interactions/GroupNameDialogFragment.java b/src/com/android/contacts/interactions/GroupNameDialogFragment.java
deleted file mode 100644
index 5efccfc..0000000
--- a/src/com/android/contacts/interactions/GroupNameDialogFragment.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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 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.InputFilter;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.EditText;
-
-import com.android.contacts.R;
-
-/**
- * A common superclass for creating and renaming groups.
- */
-// TODO: consolidate it with GroupNameEditDialogFragment
-public abstract class GroupNameDialogFragment extends DialogFragment {
-    protected abstract int getTitleResourceId();
-    protected abstract void initializeGroupLabelEditText(EditText editText);
-    protected abstract void onCompleted(String groupLabel);
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-        final LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext());
-        final View view = layoutInflater.inflate(R.layout.group_name_dialog, null);
-        final EditText editText = (EditText) view.findViewById(R.id.group_label);
-        final int maxLength = getResources().getInteger(R.integer.group_name_max_length);
-        editText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxLength) });
-        editText.setInputType(InputType.TYPE_CLASS_TEXT);
-        initializeGroupLabelEditText(editText);
-
-        builder.setTitle(getTitleResourceId());
-        builder.setView(view);
-        editText.requestFocus();
-        builder.setPositiveButton(android.R.string.ok,
-                new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialogInterface, int whichButton) {
-                        onCompleted(editText.getText().toString().trim());
-                    }
-                }
-            );
-
-        builder.setNegativeButton(android.R.string.cancel, null);
-        final AlertDialog dialog = builder.create();
-
-        dialog.setOnShowListener(new OnShowListener() {
-            @Override
-            public void onShow(DialogInterface dialogInterface) {
-                updateOkButtonState(dialog, editText);
-            }
-        });
-        editText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                updateOkButtonState(dialog, editText);
-            }
-        });
-        dialog.getWindow().setSoftInputMode(
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-        return dialog;
-    }
-
-    /* package */ void updateOkButtonState(AlertDialog dialog, EditText editText) {
-        final Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
-        okButton.setEnabled(!TextUtils.isEmpty(editText.getText().toString().trim()));
-    }
-}