blob: 3b79546be03011fb1677fc6b14a715ac6b8685dc [file] [log] [blame]
Walter Jang758f5652016-04-05 09:29:56 -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 */
16
17package com.android.contacts.group;
18
Sean Midford9970f302016-11-07 12:23:45 -080019import android.app.Fragment;
20import android.content.ContentResolver;
Walter Jang758f5652016-04-05 09:29:56 -070021import android.content.Context;
22import android.content.Intent;
23import android.database.Cursor;
24import android.net.Uri;
Wenyi Wang38860a52016-06-24 17:47:34 -070025import android.os.Bundle;
Sean Midfordff6f1bb2016-10-12 09:48:23 -070026import android.provider.ContactsContract;
Wenyi Wang38860a52016-06-24 17:47:34 -070027import android.provider.ContactsContract.Contacts;
Walter Jang758f5652016-04-05 09:29:56 -070028import android.provider.ContactsContract.Groups;
Wenyi Wang3fafbb22016-06-02 16:56:53 -070029import android.text.TextUtils;
Walter Jang758f5652016-04-05 09:29:56 -070030
31import com.android.contacts.GroupListLoader;
Wenyi Wang6927bf32016-08-15 18:31:24 -070032import com.android.contacts.activities.ContactSelectionActivity;
Sean Midfordff6f1bb2016-10-12 09:48:23 -070033import com.android.contacts.common.ContactsUtils;
Wenyi Wang38860a52016-06-24 17:47:34 -070034import com.android.contacts.common.list.ContactsSectionIndexer;
Wenyi Wang3fafbb22016-06-02 16:56:53 -070035import com.android.contacts.common.model.account.GoogleAccountType;
yaolud8effeb2016-06-29 14:42:38 -070036import com.android.contacts.list.UiIntentActions;
Walter Jang758f5652016-04-05 09:29:56 -070037
Wenyi Wang38860a52016-06-24 17:47:34 -070038import java.util.ArrayList;
Wenyi Wang3fafbb22016-06-02 16:56:53 -070039import java.util.Arrays;
40import java.util.HashSet;
Wenyi Wang38860a52016-06-24 17:47:34 -070041import java.util.List;
Wenyi Wang3fafbb22016-06-02 16:56:53 -070042import java.util.Set;
43
Walter Jang758f5652016-04-05 09:29:56 -070044/**
45 * Group utility methods.
46 */
47public final class GroupUtil {
48
Gary Mai5c1bff22016-09-30 15:10:25 -070049 public final static String ALL_GROUPS_SELECTION =
50 Groups.ACCOUNT_TYPE + " NOT NULL AND " + Groups.ACCOUNT_NAME + " NOT NULL AND "
51 + Groups.DELETED + "=0";
52
53 public final static String DEFAULT_SELECTION = ALL_GROUPS_SELECTION + " AND "
54 + Groups.AUTO_ADD + "=0 AND " + Groups.FAVORITES + "=0";
55
Wenyi Wang6927bf32016-08-15 18:31:24 -070056 public static final String ACTION_ADD_TO_GROUP = "addToGroup";
Wenyi Wangcc8f2722016-09-07 14:52:32 -070057 public static final String ACTION_CREATE_GROUP = "createGroup";
58 public static final String ACTION_DELETE_GROUP = "deleteGroup";
Wenyi Wang6927bf32016-08-15 18:31:24 -070059 public static final String ACTION_REMOVE_FROM_GROUP = "removeFromGroup";
Wenyi Wangcc8f2722016-09-07 14:52:32 -070060 public static final String ACTION_SWITCH_GROUP = "switchGroup";
61 public static final String ACTION_UPDATE_GROUP = "updateGroup";
Wenyi Wang6927bf32016-08-15 18:31:24 -070062
Sean Midford9970f302016-11-07 12:23:45 -080063 public static final int RESULT_SEND_TO_SELECTION = 100;
64
Wenyi Wang3fafbb22016-06-02 16:56:53 -070065 // System IDs of FFC groups in Google accounts
66 private static final Set<String> FFC_GROUPS =
67 new HashSet(Arrays.asList("Friends", "Family", "Coworkers"));
68
Walter Jang758f5652016-04-05 09:29:56 -070069 private GroupUtil() {
70 }
71
72 /** Returns a {@link GroupListItem} read from the given cursor and position. */
73 static GroupListItem getGroupListItem(Cursor cursor, int position) {
74 if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(position)) {
75 return null;
76 }
77 String accountName = cursor.getString(GroupListLoader.ACCOUNT_NAME);
78 String accountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE);
79 String dataSet = cursor.getString(GroupListLoader.DATA_SET);
80 long groupId = cursor.getLong(GroupListLoader.GROUP_ID);
81 String title = cursor.getString(GroupListLoader.TITLE);
82 int memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT);
Wenyi Wang3fafbb22016-06-02 16:56:53 -070083 boolean isReadOnly = cursor.getInt(GroupListLoader.IS_READ_ONLY) == 1;
84 String systemId = cursor.getString(GroupListLoader.SYSTEM_ID);
Walter Jang758f5652016-04-05 09:29:56 -070085
86 // Figure out if this is the first group for this account name / account type pair by
87 // checking the previous entry. This is to determine whether or not we need to display an
88 // account header in this item.
89 int previousIndex = position - 1;
90 boolean isFirstGroupInAccount = true;
91 if (previousIndex >= 0 && cursor.moveToPosition(previousIndex)) {
92 String previousGroupAccountName = cursor.getString(GroupListLoader.ACCOUNT_NAME);
93 String previousGroupAccountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE);
94 String previousGroupDataSet = cursor.getString(GroupListLoader.DATA_SET);
95
Walter Jangdd2d8e22016-09-09 11:07:37 -070096 if (TextUtils.equals(accountName, previousGroupAccountName)
97 && TextUtils.equals(accountType, previousGroupAccountType)
98 && TextUtils.equals(dataSet, previousGroupDataSet)) {
Walter Jang758f5652016-04-05 09:29:56 -070099 isFirstGroupInAccount = false;
100 }
101 }
102
103 return new GroupListItem(accountName, accountType, dataSet, groupId, title,
Wenyi Wang3fafbb22016-06-02 16:56:53 -0700104 isFirstGroupInAccount, memberCount, isReadOnly, systemId);
Walter Jang758f5652016-04-05 09:29:56 -0700105 }
106
Sean Midford9970f302016-11-07 12:23:45 -0800107 public static List<String> getSendToDataForIds(Context context, long[] ids, String scheme) {
108 final List<String> items = new ArrayList<>();
109 final String sIds = GroupUtil.convertArrayToString(ids);
110 final String select = (ContactsUtils.SCHEME_MAILTO.equals(scheme)
111 ? GroupMembersFragment.Query.EMAIL_SELECTION
112 + " AND " + ContactsContract.CommonDataKinds.Email._ID + " IN (" + sIds + ")"
113 : GroupMembersFragment.Query.PHONE_SELECTION
114 + " AND " + ContactsContract.CommonDataKinds.Phone._ID + " IN (" + sIds + ")");
115 final ContentResolver contentResolver = context.getContentResolver();
116 final Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI,
117 ContactsUtils.SCHEME_MAILTO.equals(scheme)
118 ? GroupMembersFragment.Query.EMAIL_PROJECTION
119 : GroupMembersFragment.Query.PHONE_PROJECTION,
120 select, null, null);
121
122 if (cursor == null) {
123 return items;
124 }
125
126 try {
127 cursor.moveToPosition(-1);
128 while (cursor.moveToNext()) {
129 final String data = cursor.getString(GroupMembersFragment.Query.DATA1);
130
131 if (!TextUtils.isEmpty(data)) {
132 items.add(data);
133 }
134 }
135 } finally {
136 cursor.close();
137 }
138
139 return items;
140 }
141
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700142 /** Returns an Intent to send emails/phones to some activity/app */
Sean Midford9970f302016-11-07 12:23:45 -0800143 public static void startSendToSelectionActivity(
144 Fragment fragment, String itemsList, String sendScheme, String title) {
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700145 final Intent intent = new Intent(Intent.ACTION_SENDTO,
146 Uri.fromParts(sendScheme, itemsList, null));
Sean Midford9970f302016-11-07 12:23:45 -0800147 fragment.startActivityForResult(
148 Intent.createChooser(intent, title), RESULT_SEND_TO_SELECTION);
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700149 }
150
151 /** Returns an Intent to pick emails/phones to send to selection (or group) */
Sean Midford4b2ccd22016-10-14 13:03:49 -0700152 public static Intent createSendToSelectionPickerIntent(Context context, long[] ids,
153 long[] defaultSelection, String sendScheme, String title) {
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700154 final Intent intent = new Intent(context, ContactSelectionActivity.class);
155 intent.setAction(UiIntentActions.ACTION_SELECT_ITEMS);
156 intent.setType(ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
157 ? ContactsContract.CommonDataKinds.Email.CONTENT_TYPE
158 : ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
Sean Midford4b2ccd22016-10-14 13:03:49 -0700159 intent.putExtra(UiIntentActions.SELECTION_ITEM_LIST, ids);
160 intent.putExtra(UiIntentActions.SELECTION_DEFAULT_SELECTION, defaultSelection);
Sean Midfordb460f442016-10-13 10:42:35 -0700161 intent.putExtra(UiIntentActions.SELECTION_SEND_SCHEME, sendScheme);
162 intent.putExtra(UiIntentActions.SELECTION_SEND_TITLE, title);
163
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700164 return intent;
165 }
166
yaolud8effeb2016-06-29 14:42:38 -0700167 /** Returns an Intent to pick contacts to add to a group. */
John Shao41c68862016-08-17 21:02:41 -0700168 public static Intent createPickMemberIntent(Context context,
Walter Jang428824e2016-09-09 13:18:35 -0700169 GroupMetaData groupMetaData, ArrayList<String> memberContactIds) {
Wenyi Wang6927bf32016-08-15 18:31:24 -0700170 final Intent intent = new Intent(context, ContactSelectionActivity.class);
John Shao41c68862016-08-17 21:02:41 -0700171 intent.setAction(Intent.ACTION_PICK);
Walter Jang1110d672016-07-25 17:55:12 +0000172 intent.setType(Groups.CONTENT_TYPE);
Walter Jang428824e2016-09-09 13:18:35 -0700173 intent.putExtra(UiIntentActions.GROUP_ACCOUNT_NAME, groupMetaData.accountName);
174 intent.putExtra(UiIntentActions.GROUP_ACCOUNT_TYPE, groupMetaData.accountType);
175 intent.putExtra(UiIntentActions.GROUP_ACCOUNT_DATA_SET, groupMetaData.dataSet);
yaolud8effeb2016-06-29 14:42:38 -0700176 intent.putExtra(UiIntentActions.GROUP_CONTACT_IDS, memberContactIds);
177 return intent;
178 }
179
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700180 public static String convertArrayToString(long[] list) {
181 if (list == null || list.length == 0) return "";
182 return Arrays.toString(list).replace("[", "").replace("]", "");
183 }
184
Sean Midfordff6f1bb2016-10-12 09:48:23 -0700185 public static long[] convertLongSetToLongArray(Set<Long> set) {
186 final Long[] contactIds = set.toArray(new Long[set.size()]);
187 final long[] result = new long[contactIds.length];
188 for (int i = 0; i < contactIds.length; i++) {
189 result[i] = contactIds[i];
190 }
191 return result;
192 }
193
194 public static long[] convertStringSetToLongArray(Set<String> set) {
195 final String[] contactIds = set.toArray(new String[set.size()]);
196 final long[] result = new long[contactIds.length];
197 for (int i = 0; i < contactIds.length; i++) {
198 try {
199 result[i] = Long.parseLong(contactIds[i]);
200 } catch (NumberFormatException e) {
201 result[i] = -1;
202 }
203 }
204 return result;
205 }
206
Walter Jang0680a312016-05-18 13:09:01 -0700207 /**
John Shaof9dd1292016-09-08 12:10:55 -0700208 * Returns true if it's an empty and read-only group and the system ID of
Wenyi Wang3fafbb22016-06-02 16:56:53 -0700209 * the group is one of "Friends", "Family" and "Coworkers".
210 */
211 public static boolean isEmptyFFCGroup(GroupListItem groupListItem) {
John Shaof9dd1292016-09-08 12:10:55 -0700212 return groupListItem.isReadOnly()
Wenyi Wang3fafbb22016-06-02 16:56:53 -0700213 && isSystemIdFFC(groupListItem.getSystemId())
214 && (groupListItem.getMemberCount() <= 0);
215 }
216
217 private static boolean isSystemIdFFC(String systemId) {
218 return !TextUtils.isEmpty(systemId) && FFC_GROUPS.contains(systemId);
219 }
Wenyi Wang9afadde2016-06-16 15:10:59 -0700220
221 /**
Wenyi Wang79675452016-08-17 10:43:28 -0700222 * Returns true the URI is a group URI.
223 */
224 public static boolean isGroupUri(Uri uri) {
225 return uri != null && uri.toString().startsWith(Groups.CONTENT_URI.toString());
226 }
227
228 /**
Wenyi Wang9afadde2016-06-16 15:10:59 -0700229 * Sort groups alphabetically and in a localized way.
230 */
231 public static String getGroupsSortOrder() {
232 return Groups.TITLE + " COLLATE LOCALIZED ASC";
233 }
Wenyi Wang38860a52016-06-24 17:47:34 -0700234
235 /**
236 * The sum of the last element in counts[] and the last element in positions[] is the total
237 * number of remaining elements in cursor. If count is more than what's in the indexer now,
238 * then we don't need to trim.
239 */
240 public static boolean needTrimming(int count, int[] counts, int[] positions) {
241 // The sum of the last element in counts[] and the last element in positions[] is
242 // the total number of remaining elements in cursor. If mCount is more than
243 // what's in the indexer now, then we don't need to trim.
244 return positions.length > 0 && counts.length > 0
245 && count <= (counts[counts.length - 1] + positions[positions.length - 1]);
246 }
247
248 /**
249 * Update Bundle extras so as to update indexer.
250 */
251 public static void updateBundle(Bundle bundle, ContactsSectionIndexer indexer,
252 List<Integer> subscripts, String[] sections, int[] counts) {
253 for (int i : subscripts) {
254 final int filteredContact = indexer.getSectionForPosition(i);
255 if (filteredContact < counts.length && filteredContact >= 0) {
256 counts[filteredContact]--;
257 if (counts[filteredContact] == 0) {
258 sections[filteredContact] = "";
259 }
260 }
261 }
262 final String[] newSections = clearEmptyString(sections);
263 bundle.putStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES, newSections);
264 final int[] newCounts = clearZeros(counts);
265 bundle.putIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS, newCounts);
266 }
267
268 private static String[] clearEmptyString(String[] strings) {
269 final List<String> list = new ArrayList<>();
270 for (String s : strings) {
271 if (!TextUtils.isEmpty(s)) {
272 list.add(s);
273 }
274 }
275 return list.toArray(new String[list.size()]);
276 }
277
278 private static int[] clearZeros(int[] numbers) {
279 final List<Integer> list = new ArrayList<>();
280 for (int n : numbers) {
281 if (n > 0) {
282 list.add(n);
283 }
284 }
285 final int[] array = new int[list.size()];
286 for(int i = 0; i < list.size(); i++) {
287 array[i] = list.get(i);
288 }
289 return array;
290 }
Marcus Hagerott2d97b5f2016-08-10 15:28:30 -0700291
292 /**
293 * Stores column ordering for the projection of a query of ContactsContract.Groups
294 */
295 public static final class GroupsProjection {
296 public final int groupId;
297 public final int title;
298 public final int summaryCount;
299 public final int systemId;
300 public final int accountName;
301 public final int accountType;
302 public final int dataSet;
303 public final int autoAdd;
304 public final int favorites;
305 public final int isReadOnly;
306 public final int deleted;
307
308 public GroupsProjection(Cursor cursor) {
309 groupId = cursor.getColumnIndex(Groups._ID);
310 title = cursor.getColumnIndex(Groups.TITLE);
311 summaryCount = cursor.getColumnIndex(Groups.SUMMARY_COUNT);
312 systemId = cursor.getColumnIndex(Groups.SYSTEM_ID);
313 accountName = cursor.getColumnIndex(Groups.ACCOUNT_NAME);
314 accountType = cursor.getColumnIndex(Groups.ACCOUNT_TYPE);
315 dataSet = cursor.getColumnIndex(Groups.DATA_SET);
316 autoAdd = cursor.getColumnIndex(Groups.AUTO_ADD);
317 favorites = cursor.getColumnIndex(Groups.FAVORITES);
318 isReadOnly = cursor.getColumnIndex(Groups.GROUP_IS_READ_ONLY);
319 deleted = cursor.getColumnIndex(Groups.DELETED);
320 }
321
322 public GroupsProjection(String[] projection) {
323 List<String> list = Arrays.asList(projection);
324 groupId = list.indexOf(Groups._ID);
325 title = list.indexOf(Groups.TITLE);
326 summaryCount = list.indexOf(Groups.SUMMARY_COUNT);
327 systemId = list.indexOf(Groups.SYSTEM_ID);
328 accountName = list.indexOf(Groups.ACCOUNT_NAME);
329 accountType = list.indexOf(Groups.ACCOUNT_TYPE);
330 dataSet = list.indexOf(Groups.DATA_SET);
331 autoAdd = list.indexOf(Groups.AUTO_ADD);
332 favorites = list.indexOf(Groups.FAVORITES);
333 isReadOnly = list.indexOf(Groups.GROUP_IS_READ_ONLY);
334 deleted = list.indexOf(Groups.DELETED);
335 }
336
337 public String getTitle(Cursor cursor) {
338 return cursor.getString(title);
339 }
340
341 public long getId(Cursor cursor) {
342 return cursor.getLong(groupId);
343 }
344
345 public String getSystemId(Cursor cursor) {
346 return cursor.getString(systemId);
347 }
348
349 public int getSummaryCount(Cursor cursor) {
350 return cursor.getInt(summaryCount);
351 }
352
353 public boolean isEmptyFFCGroup(Cursor cursor) {
354 if (accountType == -1 || isReadOnly == -1 ||
355 systemId == -1 || summaryCount == -1) {
356 throw new IllegalArgumentException("Projection is missing required columns");
357 }
358 return GoogleAccountType.ACCOUNT_TYPE.equals(cursor.getString(accountType))
359 && cursor.getInt(isReadOnly) != 0
360 && isSystemIdFFC(cursor.getString(systemId))
361 && cursor.getInt(summaryCount) <= 0;
362 }
363 }
Walter Jang6bb87182016-08-22 09:43:19 -0700364}