Home | History | Annotate | Download | only in group
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 package com.android.contacts.group;
     17 
     18 import android.app.Activity;
     19 import android.app.LoaderManager.LoaderCallbacks;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.CursorLoader;
     23 import android.content.Intent;
     24 import android.content.Loader;
     25 import android.database.Cursor;
     26 import android.database.CursorWrapper;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.drawable.Drawable;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.Message;
     33 import android.provider.ContactsContract;
     34 import android.provider.ContactsContract.Contacts;
     35 import android.support.v4.content.ContextCompat;
     36 import android.text.TextUtils;
     37 import android.util.Log;
     38 import android.view.Gravity;
     39 import android.view.LayoutInflater;
     40 import android.view.Menu;
     41 import android.view.MenuInflater;
     42 import android.view.MenuItem;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 import android.widget.Button;
     46 import android.widget.FrameLayout;
     47 import android.widget.ImageView;
     48 import android.widget.LinearLayout;
     49 import android.widget.Toast;
     50 
     51 import com.android.contacts.ContactSaveService;
     52 import com.android.contacts.ContactsUtils;
     53 import com.android.contacts.GroupMetaDataLoader;
     54 import com.android.contacts.R;
     55 import com.android.contacts.activities.ActionBarAdapter;
     56 import com.android.contacts.activities.PeopleActivity;
     57 import com.android.contacts.group.GroupMembersAdapter.GroupMembersQuery;
     58 import com.android.contacts.interactions.GroupDeletionDialogFragment;
     59 import com.android.contacts.list.ContactsRequest;
     60 import com.android.contacts.list.ContactsSectionIndexer;
     61 import com.android.contacts.list.MultiSelectContactsListFragment;
     62 import com.android.contacts.list.MultiSelectEntryContactListAdapter.DeleteContactListener;
     63 import com.android.contacts.list.UiIntentActions;
     64 import com.android.contacts.logging.ListEvent;
     65 import com.android.contacts.logging.ListEvent.ListType;
     66 import com.android.contacts.logging.Logger;
     67 import com.android.contacts.logging.ScreenEvent;
     68 import com.android.contacts.model.account.AccountWithDataSet;
     69 import com.android.contacts.util.ImplicitIntentsUtil;
     70 import com.android.contactsbind.FeedbackHelper;
     71 import com.google.common.primitives.Longs;
     72 
     73 import java.util.ArrayList;
     74 import java.util.HashMap;
     75 import java.util.HashSet;
     76 import java.util.List;
     77 import java.util.Map;
     78 import java.util.Set;
     79 
     80 /** Displays the members of a group. */
     81 public class GroupMembersFragment extends MultiSelectContactsListFragment<GroupMembersAdapter> {
     82 
     83     private static final String TAG = "GroupMembers";
     84 
     85     private static final String KEY_IS_EDIT_MODE = "editMode";
     86     private static final String KEY_GROUP_URI = "groupUri";
     87     private static final String KEY_GROUP_METADATA = "groupMetadata";
     88 
     89     public static final String TAG_GROUP_NAME_EDIT_DIALOG = "groupNameEditDialog";
     90 
     91     private static final String ARG_GROUP_URI = "groupUri";
     92 
     93     private static final int LOADER_GROUP_METADATA = 0;
     94     private static final int MSG_FAIL_TO_LOAD = 1;
     95     private static final int RESULT_GROUP_ADD_MEMBER = 100;
     96 
     97     /** Filters out duplicate contacts. */
     98     private class FilterCursorWrapper extends CursorWrapper {
     99 
    100         private int[] mIndex;
    101         private int mCount = 0;
    102         private int mPos = 0;
    103 
    104         public FilterCursorWrapper(Cursor cursor) {
    105             super(cursor);
    106 
    107             mCount = super.getCount();
    108             mIndex = new int[mCount];
    109 
    110             final List<Integer> indicesToFilter = new ArrayList<>();
    111 
    112             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    113                 Log.v(TAG, "Group members CursorWrapper start: " + mCount);
    114             }
    115 
    116             final Bundle bundle = cursor.getExtras();
    117             final String sections[] = bundle.getStringArray(Contacts
    118                     .EXTRA_ADDRESS_BOOK_INDEX_TITLES);
    119             final int counts[] = bundle.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
    120             final ContactsSectionIndexer indexer = (sections == null || counts == null)
    121                     ? null : new ContactsSectionIndexer(sections, counts);
    122 
    123             mGroupMemberContactIds.clear();
    124             for (int i = 0; i < mCount; i++) {
    125                 super.moveToPosition(i);
    126                 final String contactId = getString(GroupMembersQuery.CONTACT_ID);
    127                 if (!mGroupMemberContactIds.contains(contactId)) {
    128                     mIndex[mPos++] = i;
    129                     mGroupMemberContactIds.add(contactId);
    130                 } else {
    131                     indicesToFilter.add(i);
    132                 }
    133             }
    134 
    135             if (indexer != null && GroupUtil.needTrimming(mCount, counts, indexer.getPositions())) {
    136                 GroupUtil.updateBundle(bundle, indexer, indicesToFilter, sections, counts);
    137             }
    138 
    139             mCount = mPos;
    140             mPos = 0;
    141             super.moveToFirst();
    142 
    143             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    144                 Log.v(TAG, "Group members CursorWrapper end: " + mCount);
    145             }
    146         }
    147 
    148         @Override
    149         public boolean move(int offset) {
    150             return moveToPosition(mPos + offset);
    151         }
    152 
    153         @Override
    154         public boolean moveToNext() {
    155             return moveToPosition(mPos + 1);
    156         }
    157 
    158         @Override
    159         public boolean moveToPrevious() {
    160             return moveToPosition(mPos - 1);
    161         }
    162 
    163         @Override
    164         public boolean moveToFirst() {
    165             return moveToPosition(0);
    166         }
    167 
    168         @Override
    169         public boolean moveToLast() {
    170             return moveToPosition(mCount - 1);
    171         }
    172 
    173         @Override
    174         public boolean moveToPosition(int position) {
    175             if (position >= mCount) {
    176                 mPos = mCount;
    177                 return false;
    178             } else if (position < 0) {
    179                 mPos = -1;
    180                 return false;
    181             }
    182             mPos = mIndex[position];
    183             return super.moveToPosition(mPos);
    184         }
    185 
    186         @Override
    187         public int getCount() {
    188             return mCount;
    189         }
    190 
    191         @Override
    192         public int getPosition() {
    193             return mPos;
    194         }
    195     }
    196 
    197     private final LoaderCallbacks<Cursor> mGroupMetaDataCallbacks = new LoaderCallbacks<Cursor>() {
    198 
    199         @Override
    200         public CursorLoader onCreateLoader(int id, Bundle args) {
    201             return new GroupMetaDataLoader(mActivity, mGroupUri);
    202         }
    203 
    204         @Override
    205         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    206             if (cursor == null || cursor.isClosed() || !cursor.moveToNext()) {
    207                 Log.e(TAG, "Failed to load group metadata for " + mGroupUri);
    208                 Toast.makeText(getContext(), R.string.groupLoadErrorToast, Toast.LENGTH_SHORT)
    209                         .show();
    210                 mHandler.sendEmptyMessage(MSG_FAIL_TO_LOAD);
    211                 return;
    212             }
    213             mGroupMetaData = new GroupMetaData(getActivity(), cursor);
    214             onGroupMetadataLoaded();
    215         }
    216 
    217         @Override
    218         public void onLoaderReset(Loader<Cursor> loader) {}
    219     };
    220 
    221     private ActionBarAdapter mActionBarAdapter;
    222 
    223     private PeopleActivity mActivity;
    224 
    225     private Uri mGroupUri;
    226 
    227     private boolean mIsEditMode;
    228 
    229     private GroupMetaData mGroupMetaData;
    230 
    231     private Set<String> mGroupMemberContactIds = new HashSet();
    232 
    233     private Handler mHandler = new Handler() {
    234         @Override
    235         public void handleMessage(Message msg) {
    236             if(msg.what == MSG_FAIL_TO_LOAD) {
    237                 mActivity.onBackPressed();
    238             }
    239         }
    240     };
    241 
    242     public static GroupMembersFragment newInstance(Uri groupUri) {
    243         final Bundle args = new Bundle();
    244         args.putParcelable(ARG_GROUP_URI, groupUri);
    245 
    246         final GroupMembersFragment fragment = new GroupMembersFragment();
    247         fragment.setArguments(args);
    248         return fragment;
    249     }
    250 
    251     public GroupMembersFragment() {
    252         setPhotoLoaderEnabled(true);
    253         setSectionHeaderDisplayEnabled(true);
    254         setHasOptionsMenu(true);
    255         setListType(ListType.GROUP);
    256     }
    257 
    258     @Override
    259     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    260         if (mGroupMetaData == null) {
    261             // Hide menu options until metadata is fully loaded
    262             return;
    263         }
    264         inflater.inflate(R.menu.view_group, menu);
    265     }
    266 
    267     @Override
    268     public void onPrepareOptionsMenu(Menu menu) {
    269         final boolean isSelectionMode = mActionBarAdapter.isSelectionMode();
    270         final boolean isGroupEditable = mGroupMetaData != null && mGroupMetaData.editable;
    271         final boolean isGroupReadOnly = mGroupMetaData != null && mGroupMetaData.readOnly;
    272 
    273         setVisible(getContext(), menu, R.id.menu_multi_send_email, !mIsEditMode && !isGroupEmpty());
    274         setVisible(getContext(), menu, R.id.menu_multi_send_message,
    275                 !mIsEditMode && !isGroupEmpty());
    276         setVisible(getContext(), menu, R.id.menu_add, isGroupEditable && !isSelectionMode);
    277         setVisible(getContext(), menu, R.id.menu_rename_group,
    278                 !isGroupReadOnly && !isSelectionMode);
    279         setVisible(getContext(), menu, R.id.menu_delete_group,
    280                 !isGroupReadOnly && !isSelectionMode);
    281         setVisible(getContext(), menu, R.id.menu_edit_group,
    282                 isGroupEditable && !mIsEditMode && !isSelectionMode && !isGroupEmpty());
    283         setVisible(getContext(), menu, R.id.menu_remove_from_group,
    284                 isGroupEditable && isSelectionMode && !mIsEditMode);
    285     }
    286 
    287     private boolean isGroupEmpty() {
    288         return getAdapter() != null && getAdapter().isEmpty();
    289     }
    290 
    291     private static void setVisible(Context context, Menu menu, int id, boolean visible) {
    292         final MenuItem menuItem = menu.findItem(id);
    293         if (menuItem != null) {
    294             menuItem.setVisible(visible);
    295             final Drawable icon = menuItem.getIcon();
    296             if (icon != null) {
    297                 icon.mutate().setColorFilter(ContextCompat.getColor(context,
    298                         R.color.actionbar_icon_color), PorterDuff.Mode.SRC_ATOP);
    299             }
    300         }
    301     }
    302 
    303     /**
    304      * Helper class for cp2 query used to look up all contact's emails and phone numbers.
    305      */
    306     public static abstract class Query {
    307         public static final String EMAIL_SELECTION =
    308                 ContactsContract.Data.MIMETYPE + "='"
    309                         + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'";
    310 
    311         public static final String PHONE_SELECTION =
    312                 ContactsContract.Data.MIMETYPE + "='"
    313                         + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'";
    314 
    315         public static final String[] EMAIL_PROJECTION = {
    316                 ContactsContract.Data.CONTACT_ID,
    317                 ContactsContract.CommonDataKinds.Email._ID,
    318                 ContactsContract.Data.IS_SUPER_PRIMARY,
    319                 ContactsContract.Data.TIMES_USED,
    320                 ContactsContract.Data.DATA1
    321         };
    322 
    323         public static final String[] PHONE_PROJECTION = {
    324                 ContactsContract.Data.CONTACT_ID,
    325                 ContactsContract.CommonDataKinds.Phone._ID,
    326                 ContactsContract.Data.IS_SUPER_PRIMARY,
    327                 ContactsContract.Data.TIMES_USED,
    328                 ContactsContract.Data.DATA1
    329         };
    330 
    331         public static final int CONTACT_ID = 0;
    332         public static final int ITEM_ID = 1;
    333         public static final int PRIMARY = 2;
    334         public static final int TIMES_USED = 3;
    335         public static final int DATA1 = 4;
    336     }
    337 
    338     /**
    339      * Helper class for managing data related to contacts and emails/phone numbers.
    340      */
    341     private class ContactDataHelperClass {
    342 
    343         private List<String> items = new ArrayList<>();
    344         private String mostUsedItemId = null;
    345         private int mostUsedTimes;
    346         private String primaryItemId = null;
    347 
    348         public void addItem(String item, int timesUsed, boolean primaryFlag) {
    349             if (mostUsedItemId == null || timesUsed > mostUsedTimes) {
    350                 mostUsedItemId = item;
    351                 mostUsedTimes = timesUsed;
    352             }
    353             if (primaryFlag) {
    354                 primaryItemId = item;
    355             }
    356             items.add(item);
    357         }
    358 
    359         public boolean hasDefaultItem() {
    360             return primaryItemId != null || items.size() == 1;
    361         }
    362 
    363         public String getDefaultSelectionItemId() {
    364             return primaryItemId != null
    365                     ? primaryItemId
    366                     : mostUsedItemId;
    367         }
    368     }
    369 
    370     private void sendToGroup(long[] ids, String sendScheme, String title) {
    371         if (ids == null || ids.length == 0) return;
    372 
    373         // Get emails or phone numbers
    374         // contactMap <contact_id, contact_data>
    375         final Map<String, ContactDataHelperClass> contactMap = new HashMap<>();
    376         // itemList <item_data>
    377         final List<String> itemList = new ArrayList<>();
    378         final String sIds = GroupUtil.convertArrayToString(ids);
    379         final String select = (ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
    380                 ? Query.EMAIL_SELECTION
    381                 : Query.PHONE_SELECTION)
    382                 + " AND " + ContactsContract.Data.CONTACT_ID + " IN (" + sIds + ")";
    383         final ContentResolver contentResolver = getContext().getContentResolver();
    384         final Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI,
    385                 ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
    386                         ? Query.EMAIL_PROJECTION
    387                         : Query.PHONE_PROJECTION,
    388                 select, null, null);
    389 
    390         if (cursor == null) {
    391             return;
    392         }
    393 
    394         try {
    395             cursor.moveToPosition(-1);
    396             while (cursor.moveToNext()) {
    397                 final String contactId = cursor.getString(Query.CONTACT_ID);
    398                 final String itemId = cursor.getString(Query.ITEM_ID);
    399                 final boolean isPrimary = cursor.getInt(Query.PRIMARY) != 0;
    400                 final int timesUsed = cursor.getInt(Query.TIMES_USED);
    401                 final String data = cursor.getString(Query.DATA1);
    402 
    403                 if (!TextUtils.isEmpty(data)) {
    404                     final ContactDataHelperClass contact;
    405                     if (!contactMap.containsKey(contactId)) {
    406                         contact = new ContactDataHelperClass();
    407                         contactMap.put(contactId, contact);
    408                     } else {
    409                         contact = contactMap.get(contactId);
    410                     }
    411                     contact.addItem(itemId, timesUsed, isPrimary);
    412                     itemList.add(data);
    413                 }
    414             }
    415         } finally {
    416             cursor.close();
    417         }
    418 
    419         // Start picker if a contact does not have a default
    420         for (ContactDataHelperClass i : contactMap.values()) {
    421             if (!i.hasDefaultItem()) {
    422                 // Build list of default selected item ids
    423                 final List<Long> defaultSelection = new ArrayList<>();
    424                 for (ContactDataHelperClass j : contactMap.values()) {
    425                     final String selectionItemId = j.getDefaultSelectionItemId();
    426                     if (selectionItemId != null) {
    427                         defaultSelection.add(Long.parseLong(selectionItemId));
    428                     }
    429                 }
    430                 final long[] defaultSelectionArray = Longs.toArray(defaultSelection);
    431                 startSendToSelectionPickerActivity(ids, defaultSelectionArray, sendScheme, title);
    432                 return;
    433             }
    434         }
    435 
    436         if (itemList.size() == 0 || contactMap.size() < ids.length) {
    437             Toast.makeText(getContext(), ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
    438                             ? getString(R.string.groupSomeContactsNoEmailsToast)
    439                             : getString(R.string.groupSomeContactsNoPhonesToast),
    440                     Toast.LENGTH_LONG).show();
    441         }
    442 
    443         if (itemList.size() == 0) {
    444             return;
    445         }
    446 
    447         final String itemsString = TextUtils.join(",", itemList);
    448         GroupUtil.startSendToSelectionActivity(this, itemsString, sendScheme, title);
    449     }
    450 
    451     private void startSendToSelectionPickerActivity(long[] ids, long[] defaultSelection,
    452             String sendScheme, String title) {
    453         startActivity(GroupUtil.createSendToSelectionPickerIntent(getContext(), ids,
    454                 defaultSelection, sendScheme, title));
    455     }
    456 
    457     private void startGroupAddMemberActivity() {
    458         startActivityForResult(GroupUtil.createPickMemberIntent(getContext(), mGroupMetaData,
    459                 getMemberContactIds()), RESULT_GROUP_ADD_MEMBER);
    460     }
    461 
    462     @Override
    463     public boolean onOptionsItemSelected(MenuItem item) {
    464         final int id = item.getItemId();
    465         if (id == android.R.id.home) {
    466             mActivity.onBackPressed();
    467         } else if (id == R.id.menu_add) {
    468             startGroupAddMemberActivity();
    469         } else if (id == R.id.menu_multi_send_email) {
    470             final long[] ids = mActionBarAdapter.isSelectionMode()
    471                     ? getAdapter().getSelectedContactIdsArray()
    472                     : GroupUtil.convertStringSetToLongArray(mGroupMemberContactIds);
    473             sendToGroup(ids, ContactsUtils.SCHEME_MAILTO,
    474                     getString(R.string.menu_sendEmailOption));
    475         } else if (id == R.id.menu_multi_send_message) {
    476             final long[] ids = mActionBarAdapter.isSelectionMode()
    477                     ? getAdapter().getSelectedContactIdsArray()
    478                     : GroupUtil.convertStringSetToLongArray(mGroupMemberContactIds);
    479             sendToGroup(ids, ContactsUtils.SCHEME_SMSTO,
    480                     getString(R.string.menu_sendMessageOption));
    481         } else if (id == R.id.menu_rename_group) {
    482             GroupNameEditDialogFragment.newInstanceForUpdate(
    483                     new AccountWithDataSet(mGroupMetaData.accountName,
    484                             mGroupMetaData.accountType, mGroupMetaData.dataSet),
    485                     GroupUtil.ACTION_UPDATE_GROUP, mGroupMetaData.groupId,
    486                     mGroupMetaData.groupName).show(getFragmentManager(),
    487                     TAG_GROUP_NAME_EDIT_DIALOG);
    488         } else if (id == R.id.menu_delete_group) {
    489             deleteGroup();
    490         } else if (id == R.id.menu_edit_group) {
    491             mIsEditMode = true;
    492             mActionBarAdapter.setSelectionMode(true);
    493             displayDeleteButtons(true);
    494         } else if (id == R.id.menu_remove_from_group) {
    495             logListEvent();
    496             removeSelectedContacts();
    497         } else {
    498             return super.onOptionsItemSelected(item);
    499         }
    500         return true;
    501     }
    502 
    503     private void removeSelectedContacts() {
    504         final long[] contactIds = getAdapter().getSelectedContactIdsArray();
    505         new UpdateGroupMembersAsyncTask(UpdateGroupMembersAsyncTask.TYPE_REMOVE,
    506                 getContext(), contactIds, mGroupMetaData.groupId, mGroupMetaData.accountName,
    507                 mGroupMetaData.accountType, mGroupMetaData.dataSet).execute();
    508 
    509         mActionBarAdapter.setSelectionMode(false);
    510     }
    511 
    512     @Override
    513     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    514         if (resultCode != Activity.RESULT_OK || data == null
    515                 || requestCode != RESULT_GROUP_ADD_MEMBER) {
    516             return;
    517         }
    518 
    519         long[] contactIds = data.getLongArrayExtra(
    520                 UiIntentActions.TARGET_CONTACT_IDS_EXTRA_KEY);
    521         if (contactIds == null) {
    522             final long contactId = data.getLongExtra(
    523                     UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, -1);
    524             if (contactId > -1) {
    525                 contactIds = new long[1];
    526                 contactIds[0] = contactId;
    527             }
    528         }
    529         new UpdateGroupMembersAsyncTask(
    530                 UpdateGroupMembersAsyncTask.TYPE_ADD,
    531                 getContext(), contactIds, mGroupMetaData.groupId, mGroupMetaData.accountName,
    532                 mGroupMetaData.accountType, mGroupMetaData.dataSet).execute();
    533     }
    534 
    535     private final ActionBarAdapter.Listener mActionBarListener = new ActionBarAdapter.Listener() {
    536         @Override
    537         public void onAction(int action) {
    538             switch (action) {
    539                 case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
    540                     if (mIsEditMode) {
    541                         displayDeleteButtons(true);
    542                         mActionBarAdapter.setActionBarTitle(getString(R.string.title_edit_group));
    543                     } else {
    544                         displayCheckBoxes(true);
    545                     }
    546                     mActivity.invalidateOptionsMenu();
    547                     break;
    548                 case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
    549                     mActionBarAdapter.setSearchMode(false);
    550                     if (mIsEditMode) {
    551                         displayDeleteButtons(false);
    552                     } else {
    553                         displayCheckBoxes(false);
    554                     }
    555                     mActivity.invalidateOptionsMenu();
    556                     break;
    557                 case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
    558                     break;
    559             }
    560         }
    561 
    562         @Override
    563         public void onUpButtonPressed() {
    564             mActivity.onBackPressed();
    565         }
    566     };
    567 
    568     private final OnCheckBoxListActionListener mCheckBoxListener =
    569             new OnCheckBoxListActionListener() {
    570                 @Override
    571                 public void onStartDisplayingCheckBoxes() {
    572                     mActionBarAdapter.setSelectionMode(true);
    573                 }
    574 
    575                 @Override
    576                 public void onSelectedContactIdsChanged() {
    577                     if (mActionBarAdapter == null) {
    578                         return;
    579                     }
    580                     if (mIsEditMode) {
    581                         mActionBarAdapter.setActionBarTitle(getString(R.string.title_edit_group));
    582                     } else {
    583                         mActionBarAdapter.setSelectionCount(getSelectedContactIds().size());
    584                     }
    585                 }
    586 
    587                 @Override
    588                 public void onStopDisplayingCheckBoxes() {
    589                     mActionBarAdapter.setSelectionMode(false);
    590                 }
    591             };
    592 
    593     private void logListEvent() {
    594         Logger.logListEvent(
    595                 ListEvent.ActionType.REMOVE_LABEL,
    596                 getListType(),
    597                 getAdapter().getCount(),
    598                 /* clickedIndex */ -1,
    599                 getAdapter().getSelectedContactIdsArray().length);
    600     }
    601 
    602     private void deleteGroup() {
    603         if (getMemberCount() == 0) {
    604             final Intent intent = ContactSaveService.createGroupDeletionIntent(
    605                     getContext(), mGroupMetaData.groupId);
    606             getContext().startService(intent);
    607             mActivity.switchToAllContacts();
    608         } else {
    609             GroupDeletionDialogFragment.show(getFragmentManager(), mGroupMetaData.groupId,
    610                     mGroupMetaData.groupName);
    611         }
    612     }
    613 
    614     @Override
    615     public void onActivityCreated(Bundle savedInstanceState) {
    616         super.onActivityCreated(savedInstanceState);
    617         mActivity = (PeopleActivity) getActivity();
    618         mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener,
    619                 mActivity.getSupportActionBar(), mActivity.getToolbar(),
    620                         R.string.enter_contact_name);
    621         mActionBarAdapter.setShowHomeIcon(true);
    622         final ContactsRequest contactsRequest = new ContactsRequest();
    623         contactsRequest.setActionCode(ContactsRequest.ACTION_GROUP);
    624         mActionBarAdapter.initialize(savedInstanceState, contactsRequest);
    625         if (mGroupMetaData != null) {
    626             mActivity.setTitle(mGroupMetaData.groupName);
    627             if (mGroupMetaData.editable) {
    628                 setCheckBoxListListener(mCheckBoxListener);
    629             }
    630         }
    631     }
    632 
    633     @Override
    634     public ActionBarAdapter getActionBarAdapter() {
    635         return mActionBarAdapter;
    636     }
    637 
    638     public void displayDeleteButtons(boolean displayDeleteButtons) {
    639         getAdapter().setDisplayDeleteButtons(displayDeleteButtons);
    640     }
    641 
    642     public ArrayList<String> getMemberContactIds() {
    643         return new ArrayList<>(mGroupMemberContactIds);
    644     }
    645 
    646     public int getMemberCount() {
    647         return mGroupMemberContactIds.size();
    648     }
    649 
    650     public boolean isEditMode() {
    651         return mIsEditMode;
    652     }
    653 
    654     @Override
    655     public void onCreate(Bundle savedState) {
    656         super.onCreate(savedState);
    657         if (savedState == null) {
    658             mGroupUri = getArguments().getParcelable(ARG_GROUP_URI);
    659         } else {
    660             mIsEditMode = savedState.getBoolean(KEY_IS_EDIT_MODE);
    661             mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
    662             mGroupMetaData = savedState.getParcelable(KEY_GROUP_METADATA);
    663         }
    664         maybeAttachCheckBoxListener();
    665     }
    666 
    667     @Override
    668     public void onResume() {
    669         super.onResume();
    670         // Re-register the listener, which may have been cleared when onSaveInstanceState was
    671         // called. See also: onSaveInstanceState
    672         mActionBarAdapter.setListener(mActionBarListener);
    673     }
    674 
    675     @Override
    676     protected void startLoading() {
    677         if (mGroupMetaData == null || !mGroupMetaData.isValid()) {
    678             getLoaderManager().restartLoader(LOADER_GROUP_METADATA, null, mGroupMetaDataCallbacks);
    679         } else {
    680             onGroupMetadataLoaded();
    681         }
    682     }
    683 
    684     @Override
    685     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    686         if (data != null) {
    687             // Wait until contacts are loaded before showing the scrollbar
    688             setVisibleScrollbarEnabled(true);
    689 
    690             final FilterCursorWrapper cursorWrapper = new FilterCursorWrapper(data);
    691             bindMembersCount(cursorWrapper.getCount());
    692             super.onLoadFinished(loader, cursorWrapper);
    693             // Update state of menu items (e.g. "Remove contacts") based on number of group members.
    694             mActivity.invalidateOptionsMenu();
    695             mActionBarAdapter.updateOverflowButtonColor();
    696         }
    697     }
    698 
    699     private void bindMembersCount(int memberCount) {
    700         final View accountFilterContainer = getView().findViewById(
    701                 R.id.account_filter_header_container);
    702         final View emptyGroupView = getView().findViewById(R.id.empty_group);
    703         if (memberCount > 0) {
    704             final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
    705                     mGroupMetaData.accountName, mGroupMetaData.accountType, mGroupMetaData.dataSet);
    706             bindListHeader(getContext(), getListView(), accountFilterContainer,
    707                     accountWithDataSet, memberCount);
    708             emptyGroupView.setVisibility(View.GONE);
    709         } else {
    710             hideHeaderAndAddPadding(getContext(), getListView(), accountFilterContainer);
    711             emptyGroupView.setVisibility(View.VISIBLE);
    712         }
    713     }
    714 
    715     @Override
    716     public void onSaveInstanceState(Bundle outState) {
    717         super.onSaveInstanceState(outState);
    718         if (mActionBarAdapter != null) {
    719             mActionBarAdapter.setListener(null);
    720             mActionBarAdapter.onSaveInstanceState(outState);
    721         }
    722         outState.putBoolean(KEY_IS_EDIT_MODE, mIsEditMode);
    723         outState.putParcelable(KEY_GROUP_URI, mGroupUri);
    724         outState.putParcelable(KEY_GROUP_METADATA, mGroupMetaData);
    725     }
    726 
    727     private void onGroupMetadataLoaded() {
    728         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Loaded " + mGroupMetaData);
    729 
    730         maybeAttachCheckBoxListener();
    731 
    732         mActivity.setTitle(mGroupMetaData.groupName);
    733         mActivity.invalidateOptionsMenu();
    734         mActivity.updateDrawerGroupMenu(mGroupMetaData.groupId);
    735 
    736         // Start loading the group members
    737         super.startLoading();
    738     }
    739 
    740     private void maybeAttachCheckBoxListener() {
    741         // Don't attach the multi select check box listener if we can't edit the group
    742         if (mGroupMetaData != null && mGroupMetaData.editable) {
    743             setCheckBoxListListener(mCheckBoxListener);
    744         }
    745     }
    746 
    747     @Override
    748     protected GroupMembersAdapter createListAdapter() {
    749         final GroupMembersAdapter adapter = new GroupMembersAdapter(getContext());
    750         adapter.setSectionHeaderDisplayEnabled(true);
    751         adapter.setDisplayPhotos(true);
    752         adapter.setDeleteContactListener(new DeletionListener());
    753         return adapter;
    754     }
    755 
    756     @Override
    757     protected void configureAdapter() {
    758         super.configureAdapter();
    759         if (mGroupMetaData != null) {
    760             getAdapter().setGroupId(mGroupMetaData.groupId);
    761         }
    762     }
    763 
    764     @Override
    765     protected View inflateView(LayoutInflater inflater, ViewGroup container) {
    766         final View view = inflater.inflate(R.layout.contact_list_content, /* root */ null);
    767         final View emptyGroupView = inflater.inflate(R.layout.empty_group_view, null);
    768 
    769         final ImageView image = (ImageView) emptyGroupView.findViewById(R.id.empty_group_image);
    770         final LinearLayout.LayoutParams params =
    771                 (LinearLayout.LayoutParams) image.getLayoutParams();
    772         final int screenHeight = getResources().getDisplayMetrics().heightPixels;
    773         params.setMargins(0, screenHeight /
    774                 getResources().getInteger(R.integer.empty_group_view_image_margin_divisor), 0, 0);
    775         params.gravity = Gravity.CENTER_HORIZONTAL;
    776         image.setLayoutParams(params);
    777 
    778         final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list);
    779         contactListLayout.addView(emptyGroupView);
    780 
    781         final Button addContactsButton =
    782                 (Button) emptyGroupView.findViewById(R.id.add_member_button);
    783         addContactsButton.setOnClickListener(new View.OnClickListener() {
    784             @Override
    785             public void onClick(View v) {
    786                 startActivityForResult(GroupUtil.createPickMemberIntent(getContext(),
    787                         mGroupMetaData, getMemberContactIds()), RESULT_GROUP_ADD_MEMBER);
    788             }
    789         });
    790         return view;
    791     }
    792 
    793     @Override
    794     protected void onItemClick(int position, long id) {
    795         final Uri uri = getAdapter().getContactUri(position);
    796         if (uri == null) {
    797             return;
    798         }
    799         if (getAdapter().isDisplayingCheckBoxes()) {
    800             super.onItemClick(position, id);
    801             return;
    802         }
    803         final int count = getAdapter().getCount();
    804         Logger.logListEvent(ListEvent.ActionType.CLICK, ListEvent.ListType.GROUP, count,
    805                 /* clickedIndex */ position, /* numSelected */ 0);
    806         ImplicitIntentsUtil.startQuickContact(
    807                 getActivity(), uri, ScreenEvent.ScreenType.LIST_GROUP);
    808     }
    809 
    810     @Override
    811     protected boolean onItemLongClick(int position, long id) {
    812         if (mActivity != null && mIsEditMode) {
    813             return true;
    814         }
    815         return super.onItemLongClick(position, id);
    816     }
    817 
    818     private final class DeletionListener implements DeleteContactListener {
    819         @Override
    820         public void onContactDeleteClicked(int position) {
    821             final long contactId = getAdapter().getContactId(position);
    822             final long[] contactIds = new long[1];
    823             contactIds[0] = contactId;
    824             new UpdateGroupMembersAsyncTask(UpdateGroupMembersAsyncTask.TYPE_REMOVE,
    825                     getContext(), contactIds, mGroupMetaData.groupId, mGroupMetaData.accountName,
    826                     mGroupMetaData.accountType, mGroupMetaData.dataSet).execute();
    827         }
    828     }
    829 
    830     public GroupMetaData getGroupMetaData() {
    831         return mGroupMetaData;
    832     }
    833 
    834     public boolean isCurrentGroup(long groupId) {
    835         return mGroupMetaData != null && mGroupMetaData.groupId == groupId;
    836     }
    837 
    838     /**
    839      * Return true if the fragment is not yet added, being removed, or detached.
    840      */
    841     public boolean isInactive() {
    842         return !isAdded() || isRemoving() || isDetached();
    843     }
    844 
    845     @Override
    846     public void onDestroy() {
    847         if (mActionBarAdapter != null) {
    848             mActionBarAdapter.setListener(null);
    849         }
    850         super.onDestroy();
    851     }
    852 
    853     public void updateExistingGroupFragment(Uri newGroupUri, String action) {
    854         toastForSaveAction(action);
    855 
    856         if (isEditMode() && getGroupCount() == 1) {
    857             // If we're deleting the last group member, exit edit mode
    858             exitEditMode();
    859         } else if (!GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(action)) {
    860             mGroupUri = newGroupUri;
    861             mGroupMetaData = null; // Clear mGroupMetaData to trigger a new load.
    862             reloadData();
    863             mActivity.invalidateOptionsMenu();
    864         }
    865     }
    866 
    867     public void toastForSaveAction(String action) {
    868         int id = -1;
    869         switch(action) {
    870             case GroupUtil.ACTION_UPDATE_GROUP:
    871                 id = R.string.groupUpdatedToast;
    872                 break;
    873             case GroupUtil.ACTION_REMOVE_FROM_GROUP:
    874                 id = R.string.groupMembersRemovedToast;
    875                 break;
    876             case GroupUtil.ACTION_CREATE_GROUP:
    877                 id = R.string.groupCreatedToast;
    878                 break;
    879             case GroupUtil.ACTION_ADD_TO_GROUP:
    880                 id = R.string.groupMembersAddedToast;
    881                 break;
    882             case GroupUtil.ACTION_SWITCH_GROUP:
    883                 // No toast associated with this action.
    884                 break;
    885             default:
    886                 FeedbackHelper.sendFeedback(getContext(), TAG,
    887                         "toastForSaveAction passed unknown action: " + action,
    888                         new IllegalArgumentException("Unhandled contact save action " + action));
    889         }
    890         toast(id);
    891     }
    892 
    893     private void toast(int resId) {
    894         if (resId >= 0) {
    895             Toast.makeText(getContext(), resId, Toast.LENGTH_SHORT).show();
    896         }
    897     }
    898 
    899     private int getGroupCount() {
    900         return getAdapter() != null ? getAdapter().getCount() : -1;
    901     }
    902 
    903     public void exitEditMode() {
    904         mIsEditMode = false;
    905         mActionBarAdapter.setSelectionMode(false);
    906         displayDeleteButtons(false);
    907     }
    908 }
    909