/* * Copyright (C) 2015 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.list; import com.android.contacts.common.list.ContactListAdapter; import com.android.contacts.common.list.ContactListItemView; import com.android.contacts.common.list.DefaultContactListAdapter; import com.android.contacts.common.logging.SearchState; import com.android.contacts.list.MultiSelectEntryContactListAdapter.SelectedContactsListener; import com.android.contacts.common.logging.Logger; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; import android.text.TextUtils; import android.view.accessibility.AccessibilityEvent; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; /** * Fragment containing a contact list used for browsing contacts and optionally selecting * multiple contacts via checkboxes. */ public class MultiSelectContactsListFragment extends DefaultContactBrowseListFragment implements SelectedContactsListener { public interface OnCheckBoxListActionListener { void onStartDisplayingCheckBoxes(); void onSelectedContactIdsChanged(); void onStopDisplayingCheckBoxes(); } private static final String EXTRA_KEY_SELECTED_CONTACTS = "selected_contacts"; private static final String KEY_SEARCH_RESULT_CLICKED = "search_result_clicked"; private OnCheckBoxListActionListener mCheckBoxListListener; private boolean mSearchResultClicked; public void setCheckBoxListListener(OnCheckBoxListActionListener checkBoxListListener) { mCheckBoxListListener = checkBoxListListener; } /** * Whether a search result was clicked by the user. Tracked so that we can distinguish * between exiting the search mode after a result was clicked from existing w/o clicking * any search result. */ public boolean wasSearchResultClicked() { return mSearchResultClicked; } /** * Resets whether a search result was clicked by the user to false. */ public void resetSearchResultClicked() { mSearchResultClicked = false; } @Override public void onSelectedContactsChanged() { if (mCheckBoxListListener != null) { mCheckBoxListListener.onSelectedContactIdsChanged(); } } @Override public void onSelectedContactsChangedViaCheckBox() { if (getAdapter().getSelectedContactIds().size() == 0) { // Last checkbox has been unchecked. So we should stop displaying checkboxes. mCheckBoxListListener.onStopDisplayingCheckBoxes(); } else { onSelectedContactsChanged(); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (savedInstanceState != null) { final TreeSet<Long> selectedContactIds = (TreeSet<Long>) savedInstanceState.getSerializable(EXTRA_KEY_SELECTED_CONTACTS); getAdapter().setSelectedContactIds(selectedContactIds); if (mCheckBoxListListener != null) { mCheckBoxListListener.onSelectedContactIdsChanged(); } mSearchResultClicked = savedInstanceState.getBoolean(KEY_SEARCH_RESULT_CLICKED); } } public TreeSet<Long> getSelectedContactIds() { final MultiSelectEntryContactListAdapter adapter = getAdapter(); return adapter.getSelectedContactIds(); } @Override public MultiSelectEntryContactListAdapter getAdapter() { return (MultiSelectEntryContactListAdapter) super.getAdapter(); } @Override protected void configureAdapter() { super.configureAdapter(); getAdapter().setSelectedContactsListener(this); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable(EXTRA_KEY_SELECTED_CONTACTS, getSelectedContactIds()); outState.putBoolean(KEY_SEARCH_RESULT_CLICKED, mSearchResultClicked); } public void displayCheckBoxes(boolean displayCheckBoxes) { getAdapter().setDisplayCheckBoxes(displayCheckBoxes); if (!displayCheckBoxes) { clearCheckBoxes(); } } public void clearCheckBoxes() { getAdapter().setSelectedContactIds(new TreeSet<Long>()); } @Override protected boolean onItemLongClick(int position, long id) { final int previouslySelectedCount = getAdapter().getSelectedContactIds().size(); final Uri uri = getAdapter().getContactUri(position); final int partition = getAdapter().getPartitionForPosition(position); if (uri != null && (partition == ContactsContract.Directory.DEFAULT && (position > 0 || !getAdapter().hasProfile()))) { final String contactId = uri.getLastPathSegment(); if (!TextUtils.isEmpty(contactId)) { if (mCheckBoxListListener != null) { mCheckBoxListListener.onStartDisplayingCheckBoxes(); } getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId)); // Manually send clicked event if there is a checkbox. // See b/24098561. TalkBack will not read it otherwise. final int index = position + getListView().getHeaderViewsCount() - getListView() .getFirstVisiblePosition(); if (index >= 0 && index < getListView().getChildCount()) { getListView().getChildAt(index).sendAccessibilityEvent(AccessibilityEvent .TYPE_VIEW_CLICKED); } } } final int nowSelectedCount = getAdapter().getSelectedContactIds().size(); if (mCheckBoxListListener != null && previouslySelectedCount != 0 && nowSelectedCount == 0) { // Last checkbox has been unchecked. So we should stop displaying checkboxes. mCheckBoxListListener.onStopDisplayingCheckBoxes(); } return true; } @Override protected void onItemClick(int position, long id) { final Uri uri = getAdapter().getContactUri(position); if (uri == null) { return; } if (getAdapter().isDisplayingCheckBoxes()) { final String contactId = uri.getLastPathSegment(); if (!TextUtils.isEmpty(contactId)) { getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId)); } } else { if (isSearchMode()) { mSearchResultClicked = true; Logger.logSearchEvent(createSearchStateForSearchResultClick(position)); } super.onItemClick(position, id); } if (mCheckBoxListListener != null && getAdapter().getSelectedContactIds().size() == 0) { mCheckBoxListListener.onStopDisplayingCheckBoxes(); } } /** * Returns the state of the search results currently presented to the user. */ public SearchState createSearchState() { return createSearchState(/* selectedPosition */ -1); } /** * Returns the state of the search results presented to the user * at the time the result in the given position was clicked. */ public SearchState createSearchStateForSearchResultClick(int selectedPosition) { return createSearchState(selectedPosition); } private SearchState createSearchState(int selectedPosition) { final MultiSelectEntryContactListAdapter adapter = getAdapter(); if (adapter == null) { return null; } final SearchState searchState = new SearchState(); searchState.queryLength = adapter.getQueryString() == null ? 0 : adapter.getQueryString().length(); searchState.numPartitions = adapter.getPartitionCount(); // Set the number of results displayed to the user. Note that the adapter.getCount(), // value does not always match the number of results actually displayed to the user, // which is why we calculate it manually. final List<Integer> numResultsInEachPartition = new ArrayList<>(); for (int i = 0; i < adapter.getPartitionCount(); i++) { final Cursor cursor = adapter.getCursor(i); if (cursor == null || cursor.isClosed()) { // Something went wrong, abort. numResultsInEachPartition.clear(); break; } numResultsInEachPartition.add(cursor.getCount()); } if (!numResultsInEachPartition.isEmpty()) { int numResults = 0; for (int i = 0; i < numResultsInEachPartition.size(); i++) { numResults += numResultsInEachPartition.get(i); } searchState.numResults = numResults; } // If a selection was made, set additional search state if (selectedPosition >= 0) { searchState.selectedPartition = adapter.getPartitionForPosition(selectedPosition); searchState.selectedIndexInPartition = adapter.getOffsetInPartition(selectedPosition); final Cursor cursor = adapter.getCursor(searchState.selectedPartition); searchState.numResultsInSelectedPartition = cursor == null || cursor.isClosed() ? -1 : cursor.getCount(); // Calculate the index across all partitions if (!numResultsInEachPartition.isEmpty()) { int selectedIndex = 0; for (int i = 0; i < searchState.selectedPartition; i++) { selectedIndex += numResultsInEachPartition.get(i); } selectedIndex += searchState.selectedIndexInPartition; searchState.selectedIndex = selectedIndex; } } return searchState; } @Override protected ContactListAdapter createListAdapter() { DefaultContactListAdapter adapter = new MultiSelectEntryContactListAdapter(getContext()); adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled()); adapter.setDisplayPhotos(true); adapter.setPhotoPosition( ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false)); return adapter; } }