Delete group members one by one in edit mode

Also fixed toast message when group members
are removed by multiselect or the new
delete button in edit mode.

Bug 28707265
Bug 18641067

Change-Id: Ia3c8250b5116d07a798a2646fb6f98acee75f4c8
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 9b0977a..5caa86d 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, isGroupEditable && !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);
+            }
         }
     }
 }