Home | History | Annotate | Download | only in list
      1 /*
      2  * Copyright (C) 2015 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 
     17 package com.android.contacts.list;
     18 
     19 import com.android.contacts.common.list.ContactListAdapter;
     20 import com.android.contacts.common.list.ContactListItemView;
     21 import com.android.contacts.common.list.DefaultContactListAdapter;
     22 import com.android.contacts.common.logging.SearchState;
     23 import com.android.contacts.list.MultiSelectEntryContactListAdapter.SelectedContactsListener;
     24 import com.android.contacts.common.logging.Logger;
     25 
     26 import android.database.Cursor;
     27 import android.net.Uri;
     28 import android.os.Bundle;
     29 import android.provider.ContactsContract;
     30 import android.text.TextUtils;
     31 import android.view.accessibility.AccessibilityEvent;
     32 
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 import java.util.TreeSet;
     36 
     37 /**
     38  * Fragment containing a contact list used for browsing contacts and optionally selecting
     39  * multiple contacts via checkboxes.
     40  */
     41 public class MultiSelectContactsListFragment extends DefaultContactBrowseListFragment
     42         implements SelectedContactsListener {
     43 
     44     public interface OnCheckBoxListActionListener {
     45         void onStartDisplayingCheckBoxes();
     46         void onSelectedContactIdsChanged();
     47         void onStopDisplayingCheckBoxes();
     48     }
     49 
     50     private static final String EXTRA_KEY_SELECTED_CONTACTS = "selected_contacts";
     51 
     52     private static final String KEY_SEARCH_RESULT_CLICKED = "search_result_clicked";
     53 
     54     private OnCheckBoxListActionListener mCheckBoxListListener;
     55     private boolean mSearchResultClicked;
     56 
     57     public void setCheckBoxListListener(OnCheckBoxListActionListener checkBoxListListener) {
     58         mCheckBoxListListener = checkBoxListListener;
     59     }
     60 
     61     /**
     62      * Whether a search result was clicked by the user. Tracked so that we can distinguish
     63      * between exiting the search mode after a result was clicked from existing w/o clicking
     64      * any search result.
     65      */
     66     public boolean wasSearchResultClicked() {
     67         return mSearchResultClicked;
     68     }
     69 
     70     /**
     71      * Resets whether a search result was clicked by the user to false.
     72      */
     73     public void resetSearchResultClicked() {
     74         mSearchResultClicked = false;
     75     }
     76 
     77     @Override
     78     public void onSelectedContactsChanged() {
     79         if (mCheckBoxListListener != null) {
     80             mCheckBoxListListener.onSelectedContactIdsChanged();
     81         }
     82     }
     83 
     84     @Override
     85     public void onSelectedContactsChangedViaCheckBox() {
     86         if (getAdapter().getSelectedContactIds().size() == 0) {
     87             // Last checkbox has been unchecked. So we should stop displaying checkboxes.
     88             mCheckBoxListListener.onStopDisplayingCheckBoxes();
     89         } else {
     90             onSelectedContactsChanged();
     91         }
     92     }
     93 
     94     @Override
     95     public void onActivityCreated(Bundle savedInstanceState) {
     96         super.onActivityCreated(savedInstanceState);
     97         if (savedInstanceState != null) {
     98             final TreeSet<Long> selectedContactIds = (TreeSet<Long>)
     99                     savedInstanceState.getSerializable(EXTRA_KEY_SELECTED_CONTACTS);
    100             getAdapter().setSelectedContactIds(selectedContactIds);
    101             if (mCheckBoxListListener != null) {
    102                 mCheckBoxListListener.onSelectedContactIdsChanged();
    103             }
    104             mSearchResultClicked = savedInstanceState.getBoolean(KEY_SEARCH_RESULT_CLICKED);
    105         }
    106     }
    107 
    108     public TreeSet<Long> getSelectedContactIds() {
    109         final MultiSelectEntryContactListAdapter adapter = getAdapter();
    110         return adapter.getSelectedContactIds();
    111     }
    112 
    113     @Override
    114     public MultiSelectEntryContactListAdapter getAdapter() {
    115         return (MultiSelectEntryContactListAdapter) super.getAdapter();
    116     }
    117 
    118     @Override
    119     protected void configureAdapter() {
    120         super.configureAdapter();
    121         getAdapter().setSelectedContactsListener(this);
    122     }
    123 
    124     @Override
    125     public void onSaveInstanceState(Bundle outState) {
    126         super.onSaveInstanceState(outState);
    127         outState.putSerializable(EXTRA_KEY_SELECTED_CONTACTS, getSelectedContactIds());
    128         outState.putBoolean(KEY_SEARCH_RESULT_CLICKED, mSearchResultClicked);
    129     }
    130 
    131     public void displayCheckBoxes(boolean displayCheckBoxes) {
    132         getAdapter().setDisplayCheckBoxes(displayCheckBoxes);
    133         if (!displayCheckBoxes) {
    134             clearCheckBoxes();
    135         }
    136     }
    137 
    138     public void clearCheckBoxes() {
    139         getAdapter().setSelectedContactIds(new TreeSet<Long>());
    140     }
    141 
    142     @Override
    143     protected boolean onItemLongClick(int position, long id) {
    144         final int previouslySelectedCount = getAdapter().getSelectedContactIds().size();
    145         final Uri uri = getAdapter().getContactUri(position);
    146         final int partition = getAdapter().getPartitionForPosition(position);
    147         if (uri != null && (partition == ContactsContract.Directory.DEFAULT
    148                 && (position > 0 || !getAdapter().hasProfile()))) {
    149             final String contactId = uri.getLastPathSegment();
    150             if (!TextUtils.isEmpty(contactId)) {
    151                 if (mCheckBoxListListener != null) {
    152                     mCheckBoxListListener.onStartDisplayingCheckBoxes();
    153                 }
    154                 getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
    155                 // Manually send clicked event if there is a checkbox.
    156                 // See b/24098561.  TalkBack will not read it otherwise.
    157                 final int index = position + getListView().getHeaderViewsCount() - getListView()
    158                         .getFirstVisiblePosition();
    159                 if (index >= 0 && index < getListView().getChildCount()) {
    160                     getListView().getChildAt(index).sendAccessibilityEvent(AccessibilityEvent
    161                             .TYPE_VIEW_CLICKED);
    162                 }
    163             }
    164         }
    165         final int nowSelectedCount = getAdapter().getSelectedContactIds().size();
    166         if (mCheckBoxListListener != null
    167                 && previouslySelectedCount != 0 && nowSelectedCount == 0) {
    168             // Last checkbox has been unchecked. So we should stop displaying checkboxes.
    169             mCheckBoxListListener.onStopDisplayingCheckBoxes();
    170         }
    171         return true;
    172     }
    173 
    174     @Override
    175     protected void onItemClick(int position, long id) {
    176         final Uri uri = getAdapter().getContactUri(position);
    177         if (uri == null) {
    178             return;
    179         }
    180         if (getAdapter().isDisplayingCheckBoxes()) {
    181             final String contactId = uri.getLastPathSegment();
    182             if (!TextUtils.isEmpty(contactId)) {
    183                 getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
    184             }
    185         } else {
    186             if (isSearchMode()) {
    187                 mSearchResultClicked = true;
    188                 Logger.logSearchEvent(createSearchStateForSearchResultClick(position));
    189             }
    190             super.onItemClick(position, id);
    191         }
    192         if (mCheckBoxListListener != null && getAdapter().getSelectedContactIds().size() == 0) {
    193             mCheckBoxListListener.onStopDisplayingCheckBoxes();
    194         }
    195     }
    196 
    197     /**
    198      * Returns the state of the search results currently presented to the user.
    199      */
    200     public SearchState createSearchState() {
    201         return createSearchState(/* selectedPosition */ -1);
    202     }
    203 
    204     /**
    205      * Returns the state of the search results presented to the user
    206      * at the time the result in the given position was clicked.
    207      */
    208     public SearchState createSearchStateForSearchResultClick(int selectedPosition) {
    209         return createSearchState(selectedPosition);
    210     }
    211 
    212     private SearchState createSearchState(int selectedPosition) {
    213         final MultiSelectEntryContactListAdapter adapter = getAdapter();
    214         if (adapter == null) {
    215             return null;
    216         }
    217         final SearchState searchState = new SearchState();
    218         searchState.queryLength = adapter.getQueryString() == null
    219                 ? 0 : adapter.getQueryString().length();
    220         searchState.numPartitions = adapter.getPartitionCount();
    221 
    222         // Set the number of results displayed to the user.  Note that the adapter.getCount(),
    223         // value does not always match the number of results actually displayed to the user,
    224         // which is why we calculate it manually.
    225         final List<Integer> numResultsInEachPartition = new ArrayList<>();
    226         for (int i = 0; i < adapter.getPartitionCount(); i++) {
    227             final Cursor cursor = adapter.getCursor(i);
    228             if (cursor == null || cursor.isClosed()) {
    229                 // Something went wrong, abort.
    230                 numResultsInEachPartition.clear();
    231                 break;
    232             }
    233             numResultsInEachPartition.add(cursor.getCount());
    234         }
    235         if (!numResultsInEachPartition.isEmpty()) {
    236             int numResults = 0;
    237             for (int i = 0; i < numResultsInEachPartition.size(); i++) {
    238                 numResults += numResultsInEachPartition.get(i);
    239             }
    240             searchState.numResults = numResults;
    241         }
    242 
    243         // If a selection was made, set additional search state
    244         if (selectedPosition >= 0) {
    245             searchState.selectedPartition = adapter.getPartitionForPosition(selectedPosition);
    246             searchState.selectedIndexInPartition = adapter.getOffsetInPartition(selectedPosition);
    247             final Cursor cursor = adapter.getCursor(searchState.selectedPartition);
    248             searchState.numResultsInSelectedPartition =
    249                     cursor == null || cursor.isClosed() ? -1 : cursor.getCount();
    250 
    251             // Calculate the index across all partitions
    252             if (!numResultsInEachPartition.isEmpty()) {
    253                 int selectedIndex = 0;
    254                 for (int i = 0; i < searchState.selectedPartition; i++) {
    255                     selectedIndex += numResultsInEachPartition.get(i);
    256                 }
    257                 selectedIndex += searchState.selectedIndexInPartition;
    258                 searchState.selectedIndex = selectedIndex;
    259             }
    260         }
    261         return searchState;
    262     }
    263 
    264     @Override
    265     protected ContactListAdapter createListAdapter() {
    266         DefaultContactListAdapter adapter = new MultiSelectEntryContactListAdapter(getContext());
    267         adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
    268         adapter.setDisplayPhotos(true);
    269         adapter.setPhotoPosition(
    270                 ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
    271         return adapter;
    272     }
    273 }
    274