blob: 266e5fc78a90dcce7e3a3627ce7e60c8e03de74c [file] [log] [blame]
Walter Jang4715c042016-04-06 17:14:23 -07001/*
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 */
16package com.android.contacts.group;
17
Walter Jangec807e12016-06-27 12:10:40 -070018import android.app.Activity;
Walter Jang44e91b12016-05-22 12:37:00 -070019import android.app.LoaderManager.LoaderCallbacks;
yaolud8effeb2016-06-29 14:42:38 -070020import android.content.Context;
Walter Jang44e91b12016-05-22 12:37:00 -070021import android.content.CursorLoader;
22import android.content.Loader;
yaolud8effeb2016-06-29 14:42:38 -070023import android.content.res.Configuration;
24import android.content.res.Resources;
Walter Jang44e91b12016-05-22 12:37:00 -070025import android.database.Cursor;
Walter Jang2552c112016-06-17 14:29:09 -070026import android.database.CursorWrapper;
Walter Jang4715c042016-04-06 17:14:23 -070027import android.net.Uri;
28import android.os.Bundle;
Wenyi Wang38860a52016-06-24 17:47:34 -070029import android.provider.ContactsContract.Contacts;
yaolud8effeb2016-06-29 14:42:38 -070030import android.util.DisplayMetrics;
Walter Jang44e91b12016-05-22 12:37:00 -070031import android.util.Log;
yaolud8effeb2016-06-29 14:42:38 -070032import android.view.Gravity;
Walter Jang4715c042016-04-06 17:14:23 -070033import android.view.LayoutInflater;
Walter Jang4715c042016-04-06 17:14:23 -070034import android.view.View;
35import android.view.ViewGroup;
yaolud8effeb2016-06-29 14:42:38 -070036import android.widget.Button;
37import android.widget.FrameLayout;
38import android.widget.ImageView;
39import android.widget.LinearLayout;
Walter Jang4715c042016-04-06 17:14:23 -070040
Walter Jang44e91b12016-05-22 12:37:00 -070041import com.android.contacts.GroupMetaDataLoader;
Walter Jang4715c042016-04-06 17:14:23 -070042import com.android.contacts.R;
Walter Jangec807e12016-06-27 12:10:40 -070043import com.android.contacts.activities.GroupMembersActivity;
Wenyi Wang38860a52016-06-24 17:47:34 -070044import com.android.contacts.common.list.ContactsSectionIndexer;
Tingting Wang4e2656f2016-06-28 22:58:43 -070045import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
Walter Jangf9779ef2016-05-25 12:23:05 -070046import com.android.contacts.common.logging.ListEvent.ListType;
Walter Jang44e91b12016-05-22 12:37:00 -070047import com.android.contacts.common.model.AccountTypeManager;
48import com.android.contacts.common.model.account.AccountType;
Wenyi Wang1114fde2016-07-11 21:44:02 -070049import com.android.contacts.common.model.account.AccountWithDataSet;
Walter Jang90f2fe52016-06-17 16:38:43 -070050import com.android.contacts.group.GroupMembersAdapter.GroupMembersQuery;
Walter Jange9ea4f02016-05-10 09:39:46 -070051import com.android.contacts.list.MultiSelectContactsListFragment;
Walter Jang4715c042016-04-06 17:14:23 -070052
Walter Jang2552c112016-06-17 14:29:09 -070053import java.util.ArrayList;
54import java.util.HashSet;
Wenyi Wang38860a52016-06-24 17:47:34 -070055import java.util.List;
Walter Jang2552c112016-06-17 14:29:09 -070056import java.util.Set;
57
Walter Jang4715c042016-04-06 17:14:23 -070058/** Displays the members of a group. */
Tingting Wang4e2656f2016-06-28 22:58:43 -070059public class GroupMembersFragment extends MultiSelectContactsListFragment<GroupMembersAdapter>
60 implements MultiSelectEntryContactListAdapter.DeleteContactListener {
Walter Jang4715c042016-04-06 17:14:23 -070061
Walter Jang44e91b12016-05-22 12:37:00 -070062 private static final String TAG = "GroupMembers";
63
64 private static final String KEY_GROUP_URI = "groupUri";
Walter Jang4715c042016-04-06 17:14:23 -070065 private static final String KEY_GROUP_METADATA = "groupMetadata";
66
Walter Jang44e91b12016-05-22 12:37:00 -070067 private static final String ARG_GROUP_URI = "groupUri";
68
69 private static final int LOADER_GROUP_METADATA = 0;
Walter Jang4715c042016-04-06 17:14:23 -070070
Walter Jang90f2fe52016-06-17 16:38:43 -070071 /** Callbacks for hosts of {@link GroupMembersFragment}. */
72 public interface GroupMembersListener {
Walter Jang4715c042016-04-06 17:14:23 -070073
Walter Jang44e91b12016-05-22 12:37:00 -070074 /** Invoked after group metadata for the passed in group URI has loaded. */
75 void onGroupMetadataLoaded(GroupMetadata groupMetadata);
76
77 /** Invoked if group metadata can't be loaded for the passed in group URI. */
78 void onGroupMetadataLoadFailed();
79
Walter Jang4715c042016-04-06 17:14:23 -070080 /** Invoked when a group member in the list is clicked. */
Wenyi Wang2b943992016-05-20 17:21:35 -070081 void onGroupMemberListItemClicked(int position, Uri contactLookupUri);
Walter Jang05e71472016-06-23 19:00:10 -070082
83 /** Invoked when a the delete button for a group member in the list is clicked. */
84 void onGroupMemberListItemDeleted(int position, long contactId);
Walter Jang4715c042016-04-06 17:14:23 -070085 }
86
Walter Jang2552c112016-06-17 14:29:09 -070087 /** Filters out duplicate contacts. */
88 private class FilterCursorWrapper extends CursorWrapper {
89
90 private int[] mIndex;
91 private int mCount = 0;
92 private int mPos = 0;
93
94 public FilterCursorWrapper(Cursor cursor) {
95 super(cursor);
96
97 mCount = super.getCount();
98 mIndex = new int[mCount];
99
Wenyi Wang38860a52016-06-24 17:47:34 -0700100 final List<Integer> indicesToFilter = new ArrayList<>();
101
Walter Jang2552c112016-06-17 14:29:09 -0700102 if (Log.isLoggable(TAG, Log.VERBOSE)) {
103 Log.v(TAG, "Group members CursorWrapper start: " + mCount);
104 }
105
Wenyi Wang38860a52016-06-24 17:47:34 -0700106 final Bundle bundle = cursor.getExtras();
107 final String sections[] = bundle.getStringArray(Contacts
108 .EXTRA_ADDRESS_BOOK_INDEX_TITLES);
109 final int counts[] = bundle.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
110 final ContactsSectionIndexer indexer = (sections == null || counts == null)
111 ? null : new ContactsSectionIndexer(sections, counts);
112
Walter Jang2552c112016-06-17 14:29:09 -0700113 mGroupMemberContactIds.clear();
114 for (int i = 0; i < mCount; i++) {
115 super.moveToPosition(i);
116 final String contactId = getString(GroupMembersQuery.CONTACT_ID);
117 if (!mGroupMemberContactIds.contains(contactId)) {
118 mIndex[mPos++] = i;
119 mGroupMemberContactIds.add(contactId);
Wenyi Wang38860a52016-06-24 17:47:34 -0700120 } else {
121 indicesToFilter.add(i);
Walter Jang2552c112016-06-17 14:29:09 -0700122 }
123 }
Wenyi Wang38860a52016-06-24 17:47:34 -0700124
125 if (indexer != null && GroupUtil.needTrimming(mCount, counts, indexer.getPositions())) {
126 GroupUtil.updateBundle(bundle, indexer, indicesToFilter, sections, counts);
127 }
128
Walter Jang2552c112016-06-17 14:29:09 -0700129 mCount = mPos;
130 mPos = 0;
131 super.moveToFirst();
132
133 if (Log.isLoggable(TAG, Log.VERBOSE)) {
134 Log.v(TAG, "Group members CursorWrapper end: " + mCount);
135 }
136 }
137
138 @Override
139 public boolean move(int offset) {
140 return moveToPosition(mPos + offset);
141 }
142
143 @Override
144 public boolean moveToNext() {
145 return moveToPosition(mPos + 1);
146 }
147
148 @Override
149 public boolean moveToPrevious() {
150 return moveToPosition(mPos - 1);
151 }
152
153 @Override
154 public boolean moveToFirst() {
155 return moveToPosition(0);
156 }
157
158 @Override
159 public boolean moveToLast() {
160 return moveToPosition(mCount - 1);
161 }
162
163 @Override
164 public boolean moveToPosition(int position) {
165 if (position >= mCount || position < 0) return false;
166 return super.moveToPosition(mIndex[position]);
167 }
168
169 @Override
170 public int getCount() {
171 return mCount;
172 }
173
174 @Override
175 public int getPosition() {
176 return mPos;
177 }
178 }
179
Walter Jang44e91b12016-05-22 12:37:00 -0700180 private final LoaderCallbacks<Cursor> mGroupMetadataCallbacks = new LoaderCallbacks<Cursor>() {
181
182 @Override
183 public CursorLoader onCreateLoader(int id, Bundle args) {
184 return new GroupMetaDataLoader(getActivity(), mGroupUri);
185 }
186
187 @Override
188 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
189 if (cursor == null || cursor.isClosed() || !cursor.moveToNext()) {
190 Log.e(TAG, "Failed to load group metadata for " + mGroupUri);
191 if (mListener != null) {
192 mListener.onGroupMetadataLoadFailed();
193 }
194 return;
195 }
Walter Jang44e91b12016-05-22 12:37:00 -0700196 mGroupMetadata = new GroupMetadata();
197 mGroupMetadata.uri = mGroupUri;
198 mGroupMetadata.accountName = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
199 mGroupMetadata.accountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
200 mGroupMetadata.dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
201 mGroupMetadata.groupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
202 mGroupMetadata.groupName = cursor.getString(GroupMetaDataLoader.TITLE);
203 mGroupMetadata.readOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
204
205 final AccountTypeManager accountTypeManager =
206 AccountTypeManager.getInstance(getActivity());
207 final AccountType accountType = accountTypeManager.getAccountType(
208 mGroupMetadata.accountType, mGroupMetadata.dataSet);
209 mGroupMetadata.editable = accountType.isGroupMembershipEditable();
210
Walter Jang44e91b12016-05-22 12:37:00 -0700211 onGroupMetadataLoaded();
212 }
213
214 @Override
215 public void onLoaderReset(Loader<Cursor> loader) {}
216 };
217
218 private Uri mGroupUri;
219
Walter Jang90f2fe52016-06-17 16:38:43 -0700220 private GroupMembersListener mListener;
Walter Jang4715c042016-04-06 17:14:23 -0700221
222 private GroupMetadata mGroupMetadata;
223
Walter Jang2552c112016-06-17 14:29:09 -0700224 private Set<String> mGroupMemberContactIds = new HashSet();
225
Walter Jang90f2fe52016-06-17 16:38:43 -0700226 public static GroupMembersFragment newInstance(Uri groupUri) {
Walter Jang0680a312016-05-18 13:09:01 -0700227 final Bundle args = new Bundle();
Walter Jang44e91b12016-05-22 12:37:00 -0700228 args.putParcelable(ARG_GROUP_URI, groupUri);
Walter Jang0680a312016-05-18 13:09:01 -0700229
Walter Jang90f2fe52016-06-17 16:38:43 -0700230 final GroupMembersFragment fragment = new GroupMembersFragment();
Walter Jang0680a312016-05-18 13:09:01 -0700231 fragment.setArguments(args);
232 return fragment;
233 }
234
Walter Jang90f2fe52016-06-17 16:38:43 -0700235 public GroupMembersFragment() {
Walter Jang4715c042016-04-06 17:14:23 -0700236 setPhotoLoaderEnabled(true);
237 setSectionHeaderDisplayEnabled(true);
Walter Jangdebf9da2016-06-20 12:42:27 -0700238 setHasOptionsMenu(true);
239
Wenyi Wang2b943992016-05-20 17:21:35 -0700240 setListType(ListType.GROUP);
Walter Jang4715c042016-04-06 17:14:23 -0700241 }
242
Walter Jang90f2fe52016-06-17 16:38:43 -0700243 public void setListener(GroupMembersListener listener) {
Walter Jang44e91b12016-05-22 12:37:00 -0700244 mListener = listener;
Walter Jang4715c042016-04-06 17:14:23 -0700245 }
246
Walter Jang05e71472016-06-23 19:00:10 -0700247 public void displayDeleteButtons(boolean displayDeleteButtons) {
248 getAdapter().setDisplayDeleteButtons(displayDeleteButtons);
249 }
250
Walter Jang2552c112016-06-17 14:29:09 -0700251 public ArrayList<String> getMemberContactIds() {
252 return new ArrayList<>(mGroupMemberContactIds);
253 }
254
255 public int getMemberCount() {
256 return mGroupMemberContactIds.size();
257 }
258
Walter Jang4715c042016-04-06 17:14:23 -0700259 @Override
260 public void onCreate(Bundle savedState) {
261 super.onCreate(savedState);
Walter Jang0680a312016-05-18 13:09:01 -0700262 if (savedState == null) {
Walter Jang44e91b12016-05-22 12:37:00 -0700263 mGroupUri = getArguments().getParcelable(ARG_GROUP_URI);
Walter Jang0680a312016-05-18 13:09:01 -0700264 } else {
Walter Jang44e91b12016-05-22 12:37:00 -0700265 mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
Walter Jang4715c042016-04-06 17:14:23 -0700266 mGroupMetadata = savedState.getParcelable(KEY_GROUP_METADATA);
267 }
Walter Jang0680a312016-05-18 13:09:01 -0700268 }
269
270 @Override
Walter Jang44e91b12016-05-22 12:37:00 -0700271 protected void startLoading() {
272 if (mGroupMetadata == null || !mGroupMetadata.isValid()) {
273 getLoaderManager().restartLoader(LOADER_GROUP_METADATA, null, mGroupMetadataCallbacks);
274 } else {
275 onGroupMetadataLoaded();
276 }
Walter Jang4715c042016-04-06 17:14:23 -0700277 }
278
279 @Override
Walter Jang2552c112016-06-17 14:29:09 -0700280 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
281 if (data != null) {
Walter Jangdebf9da2016-06-20 12:42:27 -0700282 // Wait until contacts are loaded before showing the scrollbar
283 setVisibleScrollbarEnabled(true);
284
Walter Jang2552c112016-06-17 14:29:09 -0700285 final FilterCursorWrapper cursorWrapper = new FilterCursorWrapper(data);
286 bindMembersCount(cursorWrapper.getCount());
287 super.onLoadFinished(loader, cursorWrapper);
288 }
289 }
290
291 private void bindMembersCount(int memberCount) {
292 final View accountFilterContainer = getView().findViewById(
293 R.id.account_filter_header_container);
yaolud8effeb2016-06-29 14:42:38 -0700294 final View emptyGroupView = getView().findViewById(R.id.empty_group);
295 if (memberCount > 0) {
Wenyi Wang1114fde2016-07-11 21:44:02 -0700296 final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
297 mGroupMetadata.accountName, mGroupMetadata.accountType, mGroupMetadata.dataSet);
298 bindListHeader(getContext(), getListView(), accountFilterContainer,
299 accountWithDataSet, memberCount);
yaolud8effeb2016-06-29 14:42:38 -0700300 emptyGroupView.setVisibility(View.GONE);
Walter Jang2552c112016-06-17 14:29:09 -0700301 } else {
Wenyi Wang1114fde2016-07-11 21:44:02 -0700302 hideHeaderAndAddPadding(getContext(), getListView(), accountFilterContainer);
yaolud8effeb2016-06-29 14:42:38 -0700303 emptyGroupView.setVisibility(View.VISIBLE);
Walter Jang2552c112016-06-17 14:29:09 -0700304 }
305 }
306
307 @Override
Walter Jang4715c042016-04-06 17:14:23 -0700308 public void onSaveInstanceState(Bundle outState) {
309 super.onSaveInstanceState(outState);
Walter Jang44e91b12016-05-22 12:37:00 -0700310 outState.putParcelable(KEY_GROUP_URI, mGroupUri);
Walter Jang4715c042016-04-06 17:14:23 -0700311 outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
312 }
313
Walter Jang44e91b12016-05-22 12:37:00 -0700314 private void onGroupMetadataLoaded() {
315 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Loaded " + mGroupMetadata);
316
317 maybeAttachCheckBoxListener();
318
Walter Jang44e91b12016-05-22 12:37:00 -0700319 if (mListener != null) {
320 mListener.onGroupMetadataLoaded(mGroupMetadata);
321 }
322
323 // Start loading the group members
324 super.startLoading();
325 }
326
327 private void maybeAttachCheckBoxListener() {
328 // Don't attach the multi select check box listener if we can't edit the group
329 if (mGroupMetadata != null && mGroupMetadata.editable) {
330 try {
331 setCheckBoxListListener((OnCheckBoxListActionListener) getActivity());
332 } catch (ClassCastException e) {
333 throw new ClassCastException(getActivity() + " must implement " +
334 OnCheckBoxListActionListener.class.getSimpleName());
335 }
336 }
Walter Jang4715c042016-04-06 17:14:23 -0700337 }
338
339 @Override
Walter Jang90f2fe52016-06-17 16:38:43 -0700340 protected GroupMembersAdapter createListAdapter() {
341 final GroupMembersAdapter adapter = new GroupMembersAdapter(getContext());
Walter Jangdebf9da2016-06-20 12:42:27 -0700342 adapter.setSectionHeaderDisplayEnabled(true);
Walter Jang4715c042016-04-06 17:14:23 -0700343 adapter.setDisplayPhotos(true);
Tingting Wang4e2656f2016-06-28 22:58:43 -0700344 adapter.setDeleteContactListener(this);
Walter Jang4715c042016-04-06 17:14:23 -0700345 return adapter;
346 }
347
348 @Override
Walter Jang4715c042016-04-06 17:14:23 -0700349 protected void configureAdapter() {
350 super.configureAdapter();
Walter Jang44e91b12016-05-22 12:37:00 -0700351 if (mGroupMetadata != null) {
352 getAdapter().setGroupId(mGroupMetadata.groupId);
353 }
Walter Jang4715c042016-04-06 17:14:23 -0700354 }
355
356 @Override
357 protected View inflateView(LayoutInflater inflater, ViewGroup container) {
yaolud8effeb2016-06-29 14:42:38 -0700358 final View view = inflater.inflate(R.layout.contact_list_content, /* root */ null);
359 final View emptyGroupView = inflater.inflate(R.layout.empty_group_view, null);
360 final ImageView image = (ImageView) emptyGroupView.findViewById(R.id.empty_group_image);
361 final LinearLayout.LayoutParams params =
362 (LinearLayout.LayoutParams) image.getLayoutParams();
363
364 final Resources resources = getContext().getResources();
365 final DisplayMetrics metrics = resources.getDisplayMetrics();
366 final int height = metrics.heightPixels;
367 if (resources.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
368 params.setMargins(0, height / 8, 0, 0);
369 } else {
370 params.setMargins(0, height / 6, 0, 0);
371 }
372 params.gravity = Gravity.CENTER_HORIZONTAL;
373 image.setLayoutParams(params);
374
375 final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list);
376 contactListLayout.addView(emptyGroupView);
377
378 final Button addContactsButton =
379 (Button) emptyGroupView.findViewById(R.id.add_member_button);
380 addContactsButton.setOnClickListener(new View.OnClickListener() {
381 @Override
382 public void onClick(View v) {
383 ((GroupMembersActivity) getActivity()).startGroupAddMemberActivity();
384 }
385 });
386 return view;
Walter Jang4715c042016-04-06 17:14:23 -0700387 }
388
389 @Override
390 protected void onItemClick(int position, long id) {
Walter Jang8b8b30c2016-05-12 19:07:13 -0700391 final Uri uri = getAdapter().getContactUri(position);
392 if (uri == null) {
393 return;
394 }
395 if (getAdapter().isDisplayingCheckBoxes()) {
396 super.onItemClick(position, id);
397 return;
398 }
Walter Jang0680a312016-05-18 13:09:01 -0700399 if (mListener != null) {
Tingting Wang4e2656f2016-06-28 22:58:43 -0700400 final Uri contactLookupUri = getAdapter().getContactLookupUri(position);
401 mListener.onGroupMemberListItemClicked(position, contactLookupUri);
Walter Jang4715c042016-04-06 17:14:23 -0700402 }
403 }
Walter Jangec807e12016-06-27 12:10:40 -0700404
405 @Override
406 protected boolean onItemLongClick(int position, long id) {
407 final Activity activity = getActivity();
408 if (activity != null && activity instanceof GroupMembersActivity) {
409 if (((GroupMembersActivity) activity).isEditMode()) {
410 return true;
411 }
412 }
413 return super.onItemLongClick(position, id);
414 }
Tingting Wang4e2656f2016-06-28 22:58:43 -0700415
416 @Override
417 public void onContactDeleteClicked(int position) {
418 final long contactId = getAdapter().getContactId(position);
419 mListener.onGroupMemberListItemDeleted(position, contactId);
420 }
Walter Jang4715c042016-04-06 17:14:23 -0700421}