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.list; 17 18 import android.app.Activity; 19 import android.content.Loader; 20 import android.database.Cursor; 21 import android.database.CursorWrapper; 22 import android.os.Bundle; 23 import android.provider.ContactsContract.Contacts; 24 import android.util.Log; 25 import android.view.LayoutInflater; 26 import android.view.Menu; 27 import android.view.MenuInflater; 28 import android.view.MenuItem; 29 import android.view.View; 30 import android.view.ViewGroup; 31 32 import com.android.contacts.R; 33 import com.android.contacts.activities.ContactSelectionActivity; 34 import com.android.contacts.group.GroupUtil; 35 import com.android.contacts.list.ContactListAdapter.ContactQuery; 36 import com.android.contacts.model.account.AccountWithDataSet; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * Fragment containing raw contacts for a specified account that are not already in a group. 43 */ 44 public class GroupMemberPickerFragment extends 45 MultiSelectContactsListFragment<DefaultContactListAdapter> { 46 47 public static final String TAG = "GroupMemberPicker"; 48 49 private static final String KEY_ACCOUNT_NAME = "accountName"; 50 private static final String KEY_ACCOUNT_TYPE = "accountType"; 51 private static final String KEY_ACCOUNT_DATA_SET = "accountDataSet"; 52 private static final String KEY_CONTACT_IDS = "contactIds"; 53 54 private static final String ARG_ACCOUNT_NAME = "accountName"; 55 private static final String ARG_ACCOUNT_TYPE = "accountType"; 56 private static final String ARG_ACCOUNT_DATA_SET = "accountDataSet"; 57 private static final String ARG_CONTACT_IDS = "contactIds"; 58 59 /** Callbacks for host of {@link GroupMemberPickerFragment}. */ 60 public interface Listener { 61 62 /** Invoked when a potential group member is selected. */ onGroupMemberClicked(long contactId)63 void onGroupMemberClicked(long contactId); 64 65 /** Invoked when user has initiated multiple selection mode. */ onSelectGroupMembers()66 void onSelectGroupMembers(); 67 } 68 69 /** Filters out raw contacts that are already in the group. */ 70 private class FilterCursorWrapper extends CursorWrapper { 71 72 private int[] mIndex; 73 private int mCount = 0; 74 private int mPos = 0; 75 FilterCursorWrapper(Cursor cursor)76 public FilterCursorWrapper(Cursor cursor) { 77 super(cursor); 78 79 mCount = super.getCount(); 80 mIndex = new int[mCount]; 81 82 final List<Integer> indicesToFilter = new ArrayList<>(); 83 84 if (Log.isLoggable(TAG, Log.VERBOSE)) { 85 Log.v(TAG, "RawContacts CursorWrapper start: " + mCount); 86 } 87 88 final Bundle bundle = cursor.getExtras(); 89 final String sections[] = bundle.getStringArray(Contacts 90 .EXTRA_ADDRESS_BOOK_INDEX_TITLES); 91 final int counts[] = bundle.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); 92 final ContactsSectionIndexer indexer = (sections == null || counts == null) 93 ? null : new ContactsSectionIndexer(sections, counts); 94 95 for (int i = 0; i < mCount; i++) { 96 super.moveToPosition(i); 97 final String contactId = getString(ContactQuery.CONTACT_ID); 98 if (!mContactIds.contains(contactId)) { 99 mIndex[mPos++] = i; 100 } else { 101 indicesToFilter.add(i); 102 } 103 } 104 105 if (indexer != null && GroupUtil.needTrimming(mCount, counts, indexer.getPositions())) { 106 GroupUtil.updateBundle(bundle, indexer, indicesToFilter, sections, counts); 107 } 108 109 mCount = mPos; 110 mPos = 0; 111 super.moveToFirst(); 112 113 if (Log.isLoggable(TAG, Log.VERBOSE)) { 114 Log.v(TAG, "RawContacts CursorWrapper end: " + mCount); 115 } 116 } 117 118 @Override move(int offset)119 public boolean move(int offset) { 120 return moveToPosition(mPos + offset); 121 } 122 123 @Override moveToNext()124 public boolean moveToNext() { 125 return moveToPosition(mPos + 1); 126 } 127 128 @Override moveToPrevious()129 public boolean moveToPrevious() { 130 return moveToPosition(mPos - 1); 131 } 132 133 @Override moveToFirst()134 public boolean moveToFirst() { 135 return moveToPosition(0); 136 } 137 138 @Override moveToLast()139 public boolean moveToLast() { 140 return moveToPosition(mCount - 1); 141 } 142 143 @Override moveToPosition(int position)144 public boolean moveToPosition(int position) { 145 if (position >= mCount) { 146 mPos = mCount; 147 return false; 148 } else if (position < 0) { 149 mPos = -1; 150 return false; 151 } 152 mPos = mIndex[position]; 153 return super.moveToPosition(mPos); 154 } 155 156 @Override getCount()157 public int getCount() { 158 return mCount; 159 } 160 161 @Override getPosition()162 public int getPosition() { 163 return mPos; 164 } 165 } 166 167 private String mAccountName; 168 private String mAccountType; 169 private String mAccountDataSet; 170 private ArrayList<String> mContactIds; 171 private Listener mListener; 172 newInstance(String accountName, String accountType, String accountDataSet, ArrayList<String> contactIds)173 public static GroupMemberPickerFragment newInstance(String accountName, String accountType, 174 String accountDataSet, ArrayList<String> contactIds) { 175 final Bundle args = new Bundle(); 176 args.putString(ARG_ACCOUNT_NAME, accountName); 177 args.putString(ARG_ACCOUNT_TYPE, accountType); 178 args.putString(ARG_ACCOUNT_DATA_SET, accountDataSet); 179 args.putStringArrayList(ARG_CONTACT_IDS, contactIds); 180 181 final GroupMemberPickerFragment fragment = new GroupMemberPickerFragment(); 182 fragment.setArguments(args); 183 return fragment; 184 } 185 GroupMemberPickerFragment()186 public GroupMemberPickerFragment() { 187 setPhotoLoaderEnabled(true); 188 setSectionHeaderDisplayEnabled(true); 189 setHasOptionsMenu(true); 190 setDisplayDirectoryHeader(false); 191 } 192 193 @Override onCreate(Bundle savedState)194 public void onCreate(Bundle savedState) { 195 if (savedState == null) { 196 mAccountName = getArguments().getString(ARG_ACCOUNT_NAME); 197 mAccountType = getArguments().getString(ARG_ACCOUNT_TYPE); 198 mAccountDataSet = getArguments().getString(ARG_ACCOUNT_DATA_SET); 199 mContactIds = getArguments().getStringArrayList(ARG_CONTACT_IDS); 200 } else { 201 mAccountName = savedState.getString(KEY_ACCOUNT_NAME); 202 mAccountType = savedState.getString(KEY_ACCOUNT_TYPE); 203 mAccountDataSet = savedState.getString(KEY_ACCOUNT_DATA_SET); 204 mContactIds = savedState.getStringArrayList(KEY_CONTACT_IDS); 205 } 206 super.onCreate(savedState); 207 } 208 209 @Override onSaveInstanceState(Bundle outState)210 public void onSaveInstanceState(Bundle outState) { 211 super.onSaveInstanceState(outState); 212 outState.putString(KEY_ACCOUNT_NAME, mAccountName); 213 outState.putString(KEY_ACCOUNT_TYPE, mAccountType); 214 outState.putString(KEY_ACCOUNT_DATA_SET, mAccountDataSet); 215 outState.putStringArrayList(KEY_CONTACT_IDS, mContactIds); 216 } 217 setListener(Listener listener)218 public void setListener(Listener listener) { 219 mListener = listener; 220 } 221 222 @Override inflateView(LayoutInflater inflater, ViewGroup container)223 protected View inflateView(LayoutInflater inflater, ViewGroup container) { 224 return inflater.inflate(R.layout.contact_list_content, null); 225 } 226 227 @Override onLoadFinished(Loader<Cursor> loader, Cursor data)228 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 229 if (data != null) { 230 // Wait until contacts are loaded before showing the scrollbar 231 setVisibleScrollbarEnabled(true); 232 233 final FilterCursorWrapper cursorWrapper = new FilterCursorWrapper(data); 234 final View accountFilterContainer = getView().findViewById( 235 R.id.account_filter_header_container); 236 final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(mAccountName, 237 mAccountType, mAccountDataSet); 238 bindListHeader(getContext(), getListView(), accountFilterContainer, 239 accountWithDataSet, cursorWrapper.getCount()); 240 241 super.onLoadFinished(loader, cursorWrapper); 242 } 243 } 244 245 @Override createListAdapter()246 protected DefaultContactListAdapter createListAdapter() { 247 final DefaultContactListAdapter adapter = new DefaultContactListAdapter(getActivity()); 248 adapter.setFilter(ContactListFilter.createGroupMembersFilter( 249 mAccountType, mAccountName, mAccountDataSet)); 250 adapter.setSectionHeaderDisplayEnabled(true); 251 adapter.setDisplayPhotos(true); 252 return adapter; 253 } 254 255 @Override onItemClick(int position, long id)256 protected void onItemClick(int position, long id) { 257 if (getAdapter().isDisplayingCheckBoxes()) { 258 super.onItemClick(position, id); 259 return; 260 } 261 if (mListener != null) { 262 final long contactId = getAdapter().getContactId(position); 263 if (contactId > 0) { 264 mListener.onGroupMemberClicked(contactId); 265 } 266 } 267 } 268 269 @Override onCreateOptionsMenu(Menu menu, MenuInflater menuInflater)270 public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { 271 super.onCreateOptionsMenu(menu, menuInflater); 272 menuInflater.inflate(R.menu.group_member_picker, menu); 273 } 274 275 @Override onPrepareOptionsMenu(Menu menu)276 public void onPrepareOptionsMenu(Menu menu) { 277 final ContactSelectionActivity activity = getContactSelectionActivity(); 278 final boolean hasContacts = mContactIds == null ? false : mContactIds.size() > 0; 279 final boolean isSearchMode = activity == null ? false : activity.isSearchMode(); 280 final boolean isSelectionMode = activity == null ? false : activity.isSelectionMode(); 281 282 // Added in ContactSelectionActivity but we must account for selection mode 283 setVisible(menu, R.id.menu_search, !isSearchMode && !isSelectionMode); 284 setVisible(menu, R.id.menu_select, hasContacts && !isSearchMode && !isSelectionMode); 285 } 286 getContactSelectionActivity()287 private ContactSelectionActivity getContactSelectionActivity() { 288 final Activity activity = getActivity(); 289 if (activity != null && activity instanceof ContactSelectionActivity) { 290 return (ContactSelectionActivity) activity; 291 } 292 return null; 293 } 294 setVisible(Menu menu, int id, boolean visible)295 private static void setVisible(Menu menu, int id, boolean visible) { 296 final MenuItem menuItem = menu.findItem(id); 297 if (menuItem != null) { 298 menuItem.setVisible(visible); 299 } 300 } 301 302 @Override onOptionsItemSelected(MenuItem item)303 public boolean onOptionsItemSelected(MenuItem item) { 304 final int id = item.getItemId(); 305 if (id == android.R.id.home) { 306 final Activity activity = getActivity(); 307 if (activity != null) { 308 activity.onBackPressed(); 309 } 310 return true; 311 } else if (id == R.id.menu_select) { 312 if (mListener != null) { 313 mListener.onSelectGroupMembers(); 314 } 315 return true; 316 } 317 return super.onOptionsItemSelected(item); 318 } 319 } 320