• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.contacts.group;
18 
19 import android.app.Fragment;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.provider.ContactsContract;
27 import android.provider.ContactsContract.Contacts;
28 import android.provider.ContactsContract.Groups;
29 import android.text.TextUtils;
30 
31 import com.android.contacts.ContactsUtils;
32 import com.android.contacts.GroupListLoader;
33 import com.android.contacts.activities.ContactSelectionActivity;
34 import com.android.contacts.list.ContactsSectionIndexer;
35 import com.android.contacts.list.UiIntentActions;
36 import com.android.contacts.model.account.GoogleAccountType;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43 
44 /**
45  * Group utility methods.
46  */
47 public final class GroupUtil {
48 
49     public final static String ALL_GROUPS_SELECTION = Groups.DELETED + "=0";
50 
51     public final static String DEFAULT_SELECTION = ALL_GROUPS_SELECTION + " AND "
52             + Groups.AUTO_ADD + "=0 AND " + Groups.FAVORITES + "=0";
53 
54     public static final String ACTION_ADD_TO_GROUP = "addToGroup";
55     public static final String ACTION_CREATE_GROUP = "createGroup";
56     public static final String ACTION_DELETE_GROUP = "deleteGroup";
57     public static final String ACTION_REMOVE_FROM_GROUP = "removeFromGroup";
58     public static final String ACTION_SWITCH_GROUP = "switchGroup";
59     public static final String ACTION_UPDATE_GROUP = "updateGroup";
60 
61     public static final int RESULT_SEND_TO_SELECTION = 100;
62 
63     // System IDs of FFC groups in Google accounts
64     private static final Set<String> FFC_GROUPS =
65             new HashSet(Arrays.asList("Friends", "Family", "Coworkers"));
66 
GroupUtil()67     private GroupUtil() {
68     }
69 
70     /** Returns a {@link GroupListItem} read from the given cursor and position. */
getGroupListItem(Cursor cursor, int position)71     public static GroupListItem getGroupListItem(Cursor cursor, int position) {
72         if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(position)) {
73             return null;
74         }
75         String accountName = cursor.getString(GroupListLoader.ACCOUNT_NAME);
76         String accountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE);
77         String dataSet = cursor.getString(GroupListLoader.DATA_SET);
78         long groupId = cursor.getLong(GroupListLoader.GROUP_ID);
79         String title = cursor.getString(GroupListLoader.TITLE);
80         int memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT);
81         boolean isReadOnly = cursor.getInt(GroupListLoader.IS_READ_ONLY) == 1;
82         String systemId = cursor.getString(GroupListLoader.SYSTEM_ID);
83 
84         // Figure out if this is the first group for this account name / account type pair by
85         // checking the previous entry. This is to determine whether or not we need to display an
86         // account header in this item.
87         int previousIndex = position - 1;
88         boolean isFirstGroupInAccount = true;
89         if (previousIndex >= 0 && cursor.moveToPosition(previousIndex)) {
90             String previousGroupAccountName = cursor.getString(GroupListLoader.ACCOUNT_NAME);
91             String previousGroupAccountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE);
92             String previousGroupDataSet = cursor.getString(GroupListLoader.DATA_SET);
93 
94             if (TextUtils.equals(accountName, previousGroupAccountName)
95                     && TextUtils.equals(accountType, previousGroupAccountType)
96                     && TextUtils.equals(dataSet, previousGroupDataSet)) {
97                 isFirstGroupInAccount = false;
98             }
99         }
100 
101         return new GroupListItem(accountName, accountType, dataSet, groupId, title,
102                 isFirstGroupInAccount, memberCount, isReadOnly, systemId);
103     }
104 
getSendToDataForIds(Context context, long[] ids, String scheme)105     public static List<String> getSendToDataForIds(Context context, long[] ids, String scheme) {
106         final List<String> items = new ArrayList<>();
107         final String sIds = GroupUtil.convertArrayToString(ids);
108         final String select = (ContactsUtils.SCHEME_MAILTO.equals(scheme)
109                 ? GroupMembersFragment.Query.EMAIL_SELECTION
110                 + " AND " + ContactsContract.CommonDataKinds.Email._ID + " IN (" + sIds + ")"
111                 : GroupMembersFragment.Query.PHONE_SELECTION
112                 + " AND " + ContactsContract.CommonDataKinds.Phone._ID + " IN (" + sIds + ")");
113         final ContentResolver contentResolver = context.getContentResolver();
114         final Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI,
115                 ContactsUtils.SCHEME_MAILTO.equals(scheme)
116                         ? GroupMembersFragment.Query.EMAIL_PROJECTION
117                         : GroupMembersFragment.Query.PHONE_PROJECTION,
118                 select, null, null);
119 
120         if (cursor == null) {
121             return items;
122         }
123 
124         try {
125             cursor.moveToPosition(-1);
126             while (cursor.moveToNext()) {
127                 final String data = cursor.getString(GroupMembersFragment.Query.DATA1);
128 
129                 if (!TextUtils.isEmpty(data)) {
130                     items.add(data);
131                 }
132             }
133         } finally {
134             cursor.close();
135         }
136 
137         return items;
138     }
139 
140     /** Returns an Intent to send emails/phones to some activity/app */
startSendToSelectionActivity( Fragment fragment, String itemsList, String sendScheme, String title)141     public static void startSendToSelectionActivity(
142             Fragment fragment, String itemsList, String sendScheme, String title) {
143         final Intent intent = new Intent(Intent.ACTION_SENDTO,
144                 Uri.fromParts(sendScheme, itemsList, null));
145         fragment.startActivityForResult(
146                 Intent.createChooser(intent, title), RESULT_SEND_TO_SELECTION);
147     }
148 
149     /** Returns an Intent to pick emails/phones to send to selection (or group) */
createSendToSelectionPickerIntent(Context context, long[] ids, long[] defaultSelection, String sendScheme, String title)150     public static Intent createSendToSelectionPickerIntent(Context context, long[] ids,
151             long[] defaultSelection, String sendScheme, String title) {
152         final Intent intent = new Intent(context, ContactSelectionActivity.class);
153         intent.setAction(UiIntentActions.ACTION_SELECT_ITEMS);
154         intent.setType(ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
155                 ? ContactsContract.CommonDataKinds.Email.CONTENT_TYPE
156                 : ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
157         intent.putExtra(UiIntentActions.SELECTION_ITEM_LIST, ids);
158         intent.putExtra(UiIntentActions.SELECTION_DEFAULT_SELECTION, defaultSelection);
159         intent.putExtra(UiIntentActions.SELECTION_SEND_SCHEME, sendScheme);
160         intent.putExtra(UiIntentActions.SELECTION_SEND_TITLE, title);
161 
162         return intent;
163     }
164 
165     /** Returns an Intent to pick contacts to add to a group. */
createPickMemberIntent(Context context, GroupMetaData groupMetaData, ArrayList<String> memberContactIds)166     public static Intent createPickMemberIntent(Context context,
167             GroupMetaData groupMetaData, ArrayList<String> memberContactIds) {
168         final Intent intent = new Intent(context, ContactSelectionActivity.class);
169         intent.setAction(Intent.ACTION_PICK);
170         intent.setType(Groups.CONTENT_TYPE);
171         intent.putExtra(UiIntentActions.GROUP_ACCOUNT_NAME, groupMetaData.accountName);
172         intent.putExtra(UiIntentActions.GROUP_ACCOUNT_TYPE, groupMetaData.accountType);
173         intent.putExtra(UiIntentActions.GROUP_ACCOUNT_DATA_SET, groupMetaData.dataSet);
174         intent.putExtra(UiIntentActions.GROUP_CONTACT_IDS, memberContactIds);
175         return intent;
176     }
177 
convertArrayToString(long[] list)178     public static String convertArrayToString(long[] list) {
179         if (list == null || list.length == 0) return "";
180         return Arrays.toString(list).replace("[", "").replace("]", "");
181     }
182 
convertLongSetToLongArray(Set<Long> set)183     public static long[] convertLongSetToLongArray(Set<Long> set) {
184         final Long[] contactIds = set.toArray(new Long[set.size()]);
185         final long[] result = new long[contactIds.length];
186         for (int i = 0; i < contactIds.length; i++) {
187             result[i] = contactIds[i];
188         }
189         return result;
190     }
191 
convertStringSetToLongArray(Set<String> set)192     public static long[] convertStringSetToLongArray(Set<String> set) {
193         final String[] contactIds = set.toArray(new String[set.size()]);
194         final long[] result = new long[contactIds.length];
195         for (int i = 0; i < contactIds.length; i++) {
196             try {
197                 result[i] = Long.parseLong(contactIds[i]);
198             } catch (NumberFormatException e) {
199                 result[i] = -1;
200             }
201         }
202         return result;
203     }
204 
205     /**
206      * Returns true if it's an empty and read-only group and the system ID of
207      * the group is one of "Friends", "Family" and "Coworkers".
208      */
isEmptyFFCGroup(GroupListItem groupListItem)209     public static boolean isEmptyFFCGroup(GroupListItem groupListItem) {
210         return groupListItem.isReadOnly()
211                 && isSystemIdFFC(groupListItem.getSystemId())
212                 && (groupListItem.getMemberCount() <= 0);
213     }
214 
isSystemIdFFC(String systemId)215     private static boolean isSystemIdFFC(String systemId) {
216         return !TextUtils.isEmpty(systemId) && FFC_GROUPS.contains(systemId);
217     }
218 
219     /**
220      * Returns true the URI is a group URI.
221      */
isGroupUri(Uri uri)222     public static boolean isGroupUri(Uri uri) {
223         return  uri != null && uri.toString().startsWith(Groups.CONTENT_URI.toString());
224     }
225 
226     /**
227      * Sort groups alphabetically and in a localized way.
228      */
getGroupsSortOrder()229     public static String getGroupsSortOrder() {
230         return Groups.TITLE + " COLLATE LOCALIZED ASC";
231     }
232 
233     /**
234      * The sum of the last element in counts[] and the last element in positions[] is the total
235      * number of remaining elements in cursor. If count is more than what's in the indexer now,
236      * then we don't need to trim.
237      */
needTrimming(int count, int[] counts, int[] positions)238     public static boolean needTrimming(int count, int[] counts, int[] positions) {
239         // The sum of the last element in counts[] and the last element in positions[] is
240         // the total number of remaining elements in cursor. If mCount is more than
241         // what's in the indexer now, then we don't need to trim.
242         return positions.length > 0 && counts.length > 0
243                 && count <= (counts[counts.length - 1] + positions[positions.length - 1]);
244     }
245 
246     /**
247      * Update Bundle extras so as to update indexer.
248      */
updateBundle(Bundle bundle, ContactsSectionIndexer indexer, List<Integer> subscripts, String[] sections, int[] counts)249     public static void updateBundle(Bundle bundle, ContactsSectionIndexer indexer,
250             List<Integer> subscripts, String[] sections, int[] counts) {
251         for (int i : subscripts) {
252             final int filteredContact = indexer.getSectionForPosition(i);
253             if (filteredContact < counts.length && filteredContact >= 0) {
254                 counts[filteredContact]--;
255                 if (counts[filteredContact] == 0) {
256                     sections[filteredContact] = "";
257                 }
258             }
259         }
260         final String[] newSections = clearEmptyString(sections);
261         bundle.putStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES, newSections);
262         final int[] newCounts = clearZeros(counts);
263         bundle.putIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS, newCounts);
264     }
265 
clearEmptyString(String[] strings)266     private static String[] clearEmptyString(String[] strings) {
267         final List<String> list = new ArrayList<>();
268         for (String s : strings) {
269             if (!TextUtils.isEmpty(s)) {
270                 list.add(s);
271             }
272         }
273         return list.toArray(new String[list.size()]);
274     }
275 
clearZeros(int[] numbers)276     private static int[] clearZeros(int[] numbers) {
277         final List<Integer> list = new ArrayList<>();
278         for (int n : numbers) {
279             if (n > 0) {
280                 list.add(n);
281             }
282         }
283         final int[] array = new int[list.size()];
284         for(int i = 0; i < list.size(); i++) {
285             array[i] = list.get(i);
286         }
287         return array;
288     }
289 
290     /**
291      * Stores column ordering for the projection of a query of ContactsContract.Groups
292      */
293     public static final class GroupsProjection {
294         public final int groupId;
295         public final int title;
296         public final int summaryCount;
297         public final int systemId;
298         public final int accountName;
299         public final int accountType;
300         public final int dataSet;
301         public final int autoAdd;
302         public final int favorites;
303         public final int isReadOnly;
304         public final int deleted;
305 
GroupsProjection(Cursor cursor)306         public GroupsProjection(Cursor cursor) {
307             groupId = cursor.getColumnIndex(Groups._ID);
308             title = cursor.getColumnIndex(Groups.TITLE);
309             summaryCount = cursor.getColumnIndex(Groups.SUMMARY_COUNT);
310             systemId = cursor.getColumnIndex(Groups.SYSTEM_ID);
311             accountName = cursor.getColumnIndex(Groups.ACCOUNT_NAME);
312             accountType = cursor.getColumnIndex(Groups.ACCOUNT_TYPE);
313             dataSet = cursor.getColumnIndex(Groups.DATA_SET);
314             autoAdd = cursor.getColumnIndex(Groups.AUTO_ADD);
315             favorites = cursor.getColumnIndex(Groups.FAVORITES);
316             isReadOnly = cursor.getColumnIndex(Groups.GROUP_IS_READ_ONLY);
317             deleted = cursor.getColumnIndex(Groups.DELETED);
318         }
319 
GroupsProjection(String[] projection)320         public GroupsProjection(String[] projection) {
321             List<String> list = Arrays.asList(projection);
322             groupId = list.indexOf(Groups._ID);
323             title = list.indexOf(Groups.TITLE);
324             summaryCount = list.indexOf(Groups.SUMMARY_COUNT);
325             systemId = list.indexOf(Groups.SYSTEM_ID);
326             accountName = list.indexOf(Groups.ACCOUNT_NAME);
327             accountType = list.indexOf(Groups.ACCOUNT_TYPE);
328             dataSet = list.indexOf(Groups.DATA_SET);
329             autoAdd = list.indexOf(Groups.AUTO_ADD);
330             favorites = list.indexOf(Groups.FAVORITES);
331             isReadOnly = list.indexOf(Groups.GROUP_IS_READ_ONLY);
332             deleted = list.indexOf(Groups.DELETED);
333         }
334 
getTitle(Cursor cursor)335         public String getTitle(Cursor cursor) {
336             return cursor.getString(title);
337         }
338 
getId(Cursor cursor)339         public long getId(Cursor cursor) {
340             return cursor.getLong(groupId);
341         }
342 
getSystemId(Cursor cursor)343         public String getSystemId(Cursor cursor) {
344             return cursor.getString(systemId);
345         }
346 
getSummaryCount(Cursor cursor)347         public int getSummaryCount(Cursor cursor) {
348             return cursor.getInt(summaryCount);
349         }
350 
isEmptyFFCGroup(Cursor cursor)351         public boolean isEmptyFFCGroup(Cursor cursor) {
352             if (accountType == -1 || isReadOnly == -1 ||
353                     systemId == -1 || summaryCount == -1) {
354                 throw new IllegalArgumentException("Projection is missing required columns");
355             }
356             return GoogleAccountType.ACCOUNT_TYPE.equals(cursor.getString(accountType))
357                     && cursor.getInt(isReadOnly) != 0
358                     && isSystemIdFFC(cursor.getString(systemId))
359                     && cursor.getInt(summaryCount) <= 0;
360         }
361     }
362 }
363