Merge "Delete group members one by one in edit mode"
diff --git a/res/menu/view_group.xml b/res/menu/view_group.xml
index cf945fd..69a4d85 100644
--- a/res/menu/view_group.xml
+++ b/res/menu/view_group.xml
@@ -24,6 +24,12 @@
         contacts:showAsAction="ifRoom" />
 
     <item
+        android:id="@+id/menu_edit_group"
+        android:icon="@drawable/ic_create_24dp"
+        android:title="@string/menu_editGroup"
+        contacts:showAsAction="ifRoom" />
+
+    <item
         android:id="@+id/menu_rename_group"
         android:title="@string/menu_renameGroup"/>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e735c58..44ad027 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -338,7 +338,7 @@
     <string name="groupUpdatedToast">Label updated</string>
 
     <!-- Toast displayed when contacts are removed from a label. [CHAR LIMIT=50] -->
-    <string name="groupMembersRemovedToast">Removed contacts</string>
+    <string name="groupMembersRemovedToast">Removed from label</string>
 
     <!-- Toast displayed when one or more contacts is added to a label. [CHAR LIMIT=50] -->
     <string name="groupMembersAddedToast">Added to label</string>
diff --git a/src/com/android/contacts/activities/GroupMembersActivity.java b/src/com/android/contacts/activities/GroupMembersActivity.java
index e5c41ae..efedc2b 100644
--- a/src/com/android/contacts/activities/GroupMembersActivity.java
+++ b/src/com/android/contacts/activities/GroupMembersActivity.java
@@ -71,6 +71,7 @@
     private static final String KEY_IS_INSERT_ACTION = "isInsertAction";
     private static final String KEY_GROUP_URI = "groupUri";
     private static final String KEY_GROUP_METADATA = "groupMetadata";
+    private static final String KEY_IS_EDIT_MODE = "editMode";
 
     private static final String TAG_GROUP_MEMBERS = "groupMembers";
     private static final String TAG_SELECT_ACCOUNT_DIALOG = "selectAccountDialog";
@@ -118,19 +119,21 @@
             }
             final long[] rawContactIdsToAdd;
             final long[] rawContactIdsToRemove;
+            final String action;
             if (mType == TYPE_ADD) {
                 rawContactIdsToAdd = rawContactIds;
                 rawContactIdsToRemove = null;
+                action = GroupMembersActivity.ACTION_ADD_TO_GROUP;
             } else if (mType == TYPE_REMOVE) {
                 rawContactIdsToAdd = null;
                 rawContactIdsToRemove = rawContactIds;
+                action = GroupMembersActivity.ACTION_REMOVE_FROM_GROUP;
             } else {
                 throw new IllegalStateException("Unrecognized type " + mType);
             }
             return ContactSaveService.createGroupUpdateIntent(
                     mContext, mGroupId, /* newLabel */ null, rawContactIdsToAdd,
-                    rawContactIdsToRemove, GroupMembersActivity.class,
-                    GroupMembersActivity.ACTION_ADD_TO_GROUP);
+                    rawContactIdsToRemove, GroupMembersActivity.class, action);
         }
 
         // TODO(wjang): prune raw contacts that are already in the group; ContactSaveService will
@@ -177,12 +180,13 @@
 
     private ActionBarAdapter mActionBarAdapter;
 
-    private GroupMetadata mGroupMetadata;
-
     private GroupMembersFragment mMembersFragment;
 
     private Uri mGroupUri;
     private boolean mIsInsertAction;
+    private boolean mIsEditMode;
+
+    private GroupMetadata mGroupMetadata;
 
     @Override
     public void onCreate(Bundle savedState) {
@@ -192,6 +196,7 @@
         if (savedState != null) {
             mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
             mIsInsertAction = savedState.getBoolean(KEY_IS_INSERT_ACTION);
+            mIsEditMode = savedState.getBoolean(KEY_IS_EDIT_MODE);
             mGroupMetadata = savedState.getParcelable(KEY_GROUP_METADATA);
         } else {
             mGroupUri = getIntent().getData();
@@ -265,8 +270,9 @@
         if (mActionBarAdapter != null) {
             mActionBarAdapter.onSaveInstanceState(outState);
         }
-        outState.putBoolean(KEY_IS_INSERT_ACTION, mIsInsertAction);
         outState.putParcelable(KEY_GROUP_URI, mGroupUri);
+        outState.putBoolean(KEY_IS_INSERT_ACTION, mIsInsertAction);
+        outState.putBoolean(KEY_IS_EDIT_MODE, mIsEditMode);
         outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
     }
 
@@ -293,14 +299,13 @@
         super.onNewIntent(newIntent);
 
         if (isDeleteAction(newIntent.getAction())) {
-            Toast.makeText(this, R.string.groupDeletedToast, Toast.LENGTH_SHORT).show();
+            toast(R.string.groupDeletedToast);
             setResult(RESULT_OK);
             finish();
         } else if (isSaveAction(newIntent.getAction())) {
             final Uri groupUri = newIntent.getData();
             if (groupUri == null) {
-                Toast.makeText(this, R.string.groupSavedErrorToast, Toast.LENGTH_SHORT).show();
-                setResultCanceledAndFinish();
+                setResultCanceledAndFinish(R.string.groupSavedErrorToast);
                 return;
             }
             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + groupUri);
@@ -308,22 +313,14 @@
             mGroupUri = groupUri;
             mIsInsertAction = false;
 
-            Toast.makeText(this, getToastMessageForSaveAction(newIntent.getAction()),
-                    Toast.LENGTH_SHORT).show();
+            toast(getToastMessageForSaveAction(newIntent.getAction()));
 
-            mMembersFragment = GroupMembersFragment.newInstance(groupUri);
-            mMembersFragment.setListener(this);
-
-            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
-            addGroupsAndFiltersFragments(transaction);
-            transaction.replace(R.id.fragment_container_inner, mMembersFragment, TAG_GROUP_MEMBERS)
-                    .commitAllowingStateLoss();
-
-            if (mGroupMetadata != null && mGroupMetadata.editable) {
-                mMembersFragment.setCheckBoxListListener(this);
+            // If we're editing the group, don't reload the fragment so the user can
+            // continue to remove group members one by one
+            if (!mIsEditMode && !ACTION_REMOVE_FROM_GROUP.equals(newIntent.getAction())) {
+                replaceGroupMembersFragment();
+                invalidateOptionsMenu();
             }
-
-            invalidateOptionsMenu();
         }
     }
 
@@ -346,6 +343,18 @@
         throw new IllegalArgumentException("Unhanded contact save action " + action);
     }
 
+    private void replaceGroupMembersFragment() {
+        mMembersFragment = GroupMembersFragment.newInstance(mGroupUri);
+        mMembersFragment.setListener(this);
+        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        addGroupsAndFiltersFragments(transaction);
+        transaction.replace(R.id.fragment_container_inner, mMembersFragment, TAG_GROUP_MEMBERS)
+                .commitAllowingStateLoss();
+        if (mGroupMetadata != null && mGroupMetadata.editable) {
+            mMembersFragment.setCheckBoxListListener(this);
+        }
+    }
+
     @Override
     protected void onGroupMenuItemClicked(long groupId) {
         if (mGroupMetadata.groupId != groupId) {
@@ -392,7 +401,9 @@
         setVisible(menu, R.id.menu_add, isGroupEditable && !isSelectionMode);
         setVisible(menu, R.id.menu_rename_group, !isGroupReadOnly && !isSelectionMode);
         setVisible(menu, R.id.menu_delete_group, !isGroupReadOnly && !isSelectionMode);
-        setVisible(menu, R.id.menu_remove_from_group, isGroupEditable && isSelectionMode);
+        setVisible(menu, R.id.menu_edit_group, isGroupEditable && !mIsEditMode);
+        setVisible(menu, R.id.menu_remove_from_group, isGroupEditable && isSelectionMode &&
+                !mIsEditMode);
 
         return true;
     }
@@ -431,6 +442,15 @@
                 deleteGroup();
                 return true;
             }
+            case R.id.menu_edit_group: {
+                if (mMembersFragment == null) {
+                    return false;
+                }
+                mIsEditMode = true;
+                mActionBarAdapter.setSelectionMode(true);
+                mMembersFragment.displayDeleteButtons(true);
+                return true;
+            }
             case R.id.menu_remove_from_group: {
                 if (mMembersFragment == null) {
                     return false;
@@ -482,6 +502,12 @@
             mDrawer.closeDrawer(GravityCompat.START);
         } else if (mIsInsertAction) {
             finish();
+        } else if (mIsEditMode) {
+            mIsEditMode = false;
+            mActionBarAdapter.setSelectionMode(false);
+            if (mMembersFragment != null) {
+                mMembersFragment.displayDeleteButtons(false);
+            }
         } else if (mActionBarAdapter.isSelectionMode()) {
             mActionBarAdapter.setSelectionMode(false);
             if (mMembersFragment != null) {
@@ -525,14 +551,18 @@
         setResultCanceledAndFinish(-1);
     }
 
-    private void setResultCanceledAndFinish(int toastResId) {
-        if (toastResId >= 0) {
-            Toast.makeText(this, toastResId, Toast.LENGTH_SHORT).show();
-        }
+    private void setResultCanceledAndFinish(int resId) {
+        toast(resId);
         setResult(RESULT_CANCELED);
         finish();
     }
 
+    private void toast(int resId) {
+        if (resId >= 0) {
+            Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();
+        }
+    }
+
     // SelectAccountDialogFragment.Listener callbacks
 
     @Override
@@ -554,7 +584,11 @@
         switch (action) {
             case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
                 if (mMembersFragment != null) {
-                    mMembersFragment.displayCheckBoxes(true);
+                    if (mIsEditMode) {
+                        mMembersFragment.displayDeleteButtons(true);
+                    } else {
+                        mMembersFragment.displayCheckBoxes(true);
+                    }
                 }
                 invalidateOptionsMenu();
                 showFabWithAnimation(/* showFabWithAnimation = */ false);
@@ -562,7 +596,11 @@
             case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
                 mActionBarAdapter.setSearchMode(false);
                 if (mMembersFragment != null) {
-                    mMembersFragment.displayCheckBoxes(false);
+                    if (mIsEditMode) {
+                        mMembersFragment.displayDeleteButtons(false);
+                    } else {
+                        mMembersFragment.displayCheckBoxes(false);
+                    }
                 }
                 invalidateOptionsMenu();
                 showFabWithAnimation(/* showFabWithAnimation */ true);
@@ -661,4 +699,13 @@
         intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.LIST_GROUP);
         startActivity(intent);
     }
+
+    @Override
+    public void onGroupMemberListItemDeleted(int position, long contactId) {
+        final long[] contactIds = new long[1];
+        contactIds[0] = contactId;
+        new UpdateGroupMembersAsyncTask(UpdateGroupMembersAsyncTask.TYPE_REMOVE,
+                this, contactIds, mGroupMetadata.groupId, mGroupMetadata.accountName,
+                mGroupMetadata.accountType).execute();
+    }
 }
diff --git a/src/com/android/contacts/group/GroupMembersAdapter.java b/src/com/android/contacts/group/GroupMembersAdapter.java
index a4d69cf..ae324a1 100644
--- a/src/com/android/contacts/group/GroupMembersAdapter.java
+++ b/src/com/android/contacts/group/GroupMembersAdapter.java
@@ -69,6 +69,7 @@
 
     private final CharSequence mUnknownNameText;
     private long mGroupId;
+    private boolean mDisplayDeleteButtons;
 
     public GroupMembersAdapter(Context context) {
         super(context, GroupMembersQuery.CONTACT_ID);
@@ -89,6 +90,21 @@
         return Contacts.getLookupUri(contactId, lookupKey);
     }
 
+    /** Returns the ID of the contact at the given position in the underlying cursor. */
+    public long getContactId(int position) {
+        final Cursor cursor = (Cursor) getItem(position);
+        return cursor.getLong(GroupMembersQuery.CONTACT_ID);
+    }
+
+    public void setDisplayDeleteButtons(boolean displayDeleteButtons) {
+        mDisplayDeleteButtons = displayDeleteButtons;
+        notifyDataSetChanged();
+    }
+
+    public boolean getDisplayDeleteButtons() {
+        return mDisplayDeleteButtons;
+    }
+
     @Override
     public void configureLoader(CursorLoader loader, long directoryId) {
         loader.setUri(Data.CONTENT_URI.buildUpon()
@@ -143,6 +159,7 @@
         bindSectionHeaderAndDivider(view, position);
         bindName(view, cursor);
         bindPhoto(view, cursor);
+        bindDeleteButton(view);
     }
 
     protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
@@ -170,4 +187,12 @@
         getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(),
                 imageRequest);
     }
+
+    private void bindDeleteButton(final ContactListItemView view) {
+        if (mDisplayDeleteButtons) {
+            view.getDeleteImageButton();
+        } else {
+            view.hideDeleteImageButton();
+        }
+    }
 }
diff --git a/src/com/android/contacts/group/GroupMembersFragment.java b/src/com/android/contacts/group/GroupMembersFragment.java
index c65bf49..7d98173 100644
--- a/src/com/android/contacts/group/GroupMembersFragment.java
+++ b/src/com/android/contacts/group/GroupMembersFragment.java
@@ -63,6 +63,9 @@
 
         /** Invoked when a group member in the list is clicked. */
         void onGroupMemberListItemClicked(int position, Uri contactLookupUri);
+
+        /** Invoked when a the delete button for a group member in the list is clicked. */
+        void onGroupMemberListItemDeleted(int position, long contactId);
     }
 
     /** Filters out duplicate contacts. */
@@ -209,6 +212,10 @@
         mListener = listener;
     }
 
+    public void displayDeleteButtons(boolean displayDeleteButtons) {
+        getAdapter().setDisplayDeleteButtons(displayDeleteButtons);
+    }
+
     public ArrayList<String> getMemberContactIds() {
         return  new ArrayList<>(mGroupMemberContactIds);
     }
@@ -328,8 +335,13 @@
             return;
         }
         if (mListener != null) {
-            final Uri contactLookupUri = getAdapter().getContactLookupUri(position);
-            mListener.onGroupMemberListItemClicked(position, contactLookupUri);
+            if (getAdapter().getDisplayDeleteButtons()) {
+                final long contactId = getAdapter().getContactId(position);
+                mListener.onGroupMemberListItemDeleted(position, contactId);
+            } else {
+                final Uri contactLookupUri = getAdapter().getContactLookupUri(position);
+                mListener.onGroupMemberListItemClicked(position, contactLookupUri);
+            }
         }
     }
 }