| /* |
| * Copyright (C) 2016 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.group; |
| |
| import android.app.Fragment; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Groups; |
| import android.text.TextUtils; |
| |
| import com.android.contacts.ContactsUtils; |
| import com.android.contacts.GroupListLoader; |
| import com.android.contacts.activities.ContactSelectionActivity; |
| import com.android.contacts.list.ContactsSectionIndexer; |
| import com.android.contacts.list.UiIntentActions; |
| import com.android.contacts.model.account.GoogleAccountType; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Group utility methods. |
| */ |
| public final class GroupUtil { |
| |
| public final static String ALL_GROUPS_SELECTION = Groups.DELETED + "=0"; |
| |
| public final static String DEFAULT_SELECTION = ALL_GROUPS_SELECTION + " AND " |
| + Groups.AUTO_ADD + "=0 AND " + Groups.FAVORITES + "=0"; |
| |
| public static final String ACTION_ADD_TO_GROUP = "addToGroup"; |
| public static final String ACTION_CREATE_GROUP = "createGroup"; |
| public static final String ACTION_DELETE_GROUP = "deleteGroup"; |
| public static final String ACTION_REMOVE_FROM_GROUP = "removeFromGroup"; |
| public static final String ACTION_SWITCH_GROUP = "switchGroup"; |
| public static final String ACTION_UPDATE_GROUP = "updateGroup"; |
| |
| public static final int RESULT_GROUP_ADD_MEMBER = 100; |
| public static final int RESULT_SEND_TO_SELECTION = 200; |
| |
| // System IDs of FFC groups in Google accounts |
| private static final Set<String> FFC_GROUPS = |
| new HashSet(Arrays.asList("Friends", "Family", "Coworkers")); |
| |
| private GroupUtil() { |
| } |
| |
| /** Returns a {@link GroupListItem} read from the given cursor and position. */ |
| public static GroupListItem getGroupListItem(Cursor cursor, int position) { |
| if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(position)) { |
| return null; |
| } |
| String accountName = cursor.getString(GroupListLoader.ACCOUNT_NAME); |
| String accountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE); |
| String dataSet = cursor.getString(GroupListLoader.DATA_SET); |
| long groupId = cursor.getLong(GroupListLoader.GROUP_ID); |
| String title = cursor.getString(GroupListLoader.TITLE); |
| int memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT); |
| boolean isReadOnly = cursor.getInt(GroupListLoader.IS_READ_ONLY) == 1; |
| String systemId = cursor.getString(GroupListLoader.SYSTEM_ID); |
| |
| // Figure out if this is the first group for this account name / account type pair by |
| // checking the previous entry. This is to determine whether or not we need to display an |
| // account header in this item. |
| int previousIndex = position - 1; |
| boolean isFirstGroupInAccount = true; |
| if (previousIndex >= 0 && cursor.moveToPosition(previousIndex)) { |
| String previousGroupAccountName = cursor.getString(GroupListLoader.ACCOUNT_NAME); |
| String previousGroupAccountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE); |
| String previousGroupDataSet = cursor.getString(GroupListLoader.DATA_SET); |
| |
| if (TextUtils.equals(accountName, previousGroupAccountName) |
| && TextUtils.equals(accountType, previousGroupAccountType) |
| && TextUtils.equals(dataSet, previousGroupDataSet)) { |
| isFirstGroupInAccount = false; |
| } |
| } |
| |
| return new GroupListItem(accountName, accountType, dataSet, groupId, title, |
| isFirstGroupInAccount, memberCount, isReadOnly, systemId); |
| } |
| |
| public static List<String> getSendToDataForIds(Context context, long[] ids, String scheme) { |
| final List<String> items = new ArrayList<>(); |
| final String sIds = GroupUtil.convertArrayToString(ids); |
| final String select = (ContactsUtils.SCHEME_MAILTO.equals(scheme) |
| ? GroupMembersFragment.Query.EMAIL_SELECTION |
| + " AND " + ContactsContract.CommonDataKinds.Email._ID + " IN (" + sIds + ")" |
| : GroupMembersFragment.Query.PHONE_SELECTION |
| + " AND " + ContactsContract.CommonDataKinds.Phone._ID + " IN (" + sIds + ")"); |
| final ContentResolver contentResolver = context.getContentResolver(); |
| final Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI, |
| ContactsUtils.SCHEME_MAILTO.equals(scheme) |
| ? GroupMembersFragment.Query.EMAIL_PROJECTION |
| : GroupMembersFragment.Query.PHONE_PROJECTION, |
| select, null, null); |
| |
| if (cursor == null) { |
| return items; |
| } |
| |
| try { |
| cursor.moveToPosition(-1); |
| while (cursor.moveToNext()) { |
| final String data = cursor.getString(GroupMembersFragment.Query.DATA1); |
| |
| if (!TextUtils.isEmpty(data)) { |
| items.add(data); |
| } |
| } |
| } finally { |
| cursor.close(); |
| } |
| |
| return items; |
| } |
| |
| /** Returns an Intent to send emails/phones to some activity/app */ |
| public static void startSendToSelectionActivity( |
| Fragment fragment, String itemsList, String sendScheme, String title) { |
| final Intent intent = new Intent(Intent.ACTION_SENDTO, |
| Uri.fromParts(sendScheme, itemsList, null)); |
| fragment.startActivityForResult( |
| Intent.createChooser(intent, title), RESULT_SEND_TO_SELECTION); |
| } |
| |
| /** Returns an Intent to pick emails/phones to send to selection (or group) */ |
| public static Intent createSendToSelectionPickerIntent(Context context, long[] ids, |
| long[] defaultSelection, String sendScheme, String title) { |
| final Intent intent = new Intent(context, ContactSelectionActivity.class); |
| intent.setAction(UiIntentActions.ACTION_SELECT_ITEMS); |
| intent.setType(ContactsUtils.SCHEME_MAILTO.equals(sendScheme) |
| ? ContactsContract.CommonDataKinds.Email.CONTENT_TYPE |
| : ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); |
| intent.putExtra(UiIntentActions.SELECTION_ITEM_LIST, ids); |
| intent.putExtra(UiIntentActions.SELECTION_DEFAULT_SELECTION, defaultSelection); |
| intent.putExtra(UiIntentActions.SELECTION_SEND_SCHEME, sendScheme); |
| intent.putExtra(UiIntentActions.SELECTION_SEND_TITLE, title); |
| |
| return intent; |
| } |
| |
| /** Returns an Intent to pick contacts to add to a group. */ |
| public static Intent createPickMemberIntent(Context context, |
| GroupMetaData groupMetaData, ArrayList<String> memberContactIds) { |
| final Intent intent = new Intent(context, ContactSelectionActivity.class); |
| intent.setAction(Intent.ACTION_PICK); |
| intent.setType(Groups.CONTENT_TYPE); |
| intent.putExtra(UiIntentActions.GROUP_ACCOUNT_NAME, groupMetaData.accountName); |
| intent.putExtra(UiIntentActions.GROUP_ACCOUNT_TYPE, groupMetaData.accountType); |
| intent.putExtra(UiIntentActions.GROUP_ACCOUNT_DATA_SET, groupMetaData.dataSet); |
| intent.putExtra(UiIntentActions.GROUP_CONTACT_IDS, memberContactIds); |
| return intent; |
| } |
| |
| public static String convertArrayToString(long[] list) { |
| if (list == null || list.length == 0) return ""; |
| return Arrays.toString(list).replace("[", "").replace("]", ""); |
| } |
| |
| public static long[] convertLongSetToLongArray(Set<Long> set) { |
| final Long[] contactIds = set.toArray(new Long[set.size()]); |
| final long[] result = new long[contactIds.length]; |
| for (int i = 0; i < contactIds.length; i++) { |
| result[i] = contactIds[i]; |
| } |
| return result; |
| } |
| |
| public static long[] convertStringSetToLongArray(Set<String> set) { |
| final String[] contactIds = set.toArray(new String[set.size()]); |
| final long[] result = new long[contactIds.length]; |
| for (int i = 0; i < contactIds.length; i++) { |
| try { |
| result[i] = Long.parseLong(contactIds[i]); |
| } catch (NumberFormatException e) { |
| result[i] = -1; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Returns true if it's an empty and read-only group and the system ID of |
| * the group is one of "Friends", "Family" and "Coworkers". |
| */ |
| public static boolean isEmptyFFCGroup(GroupListItem groupListItem) { |
| return groupListItem.isReadOnly() |
| && isSystemIdFFC(groupListItem.getSystemId()) |
| && (groupListItem.getMemberCount() <= 0); |
| } |
| |
| private static boolean isSystemIdFFC(String systemId) { |
| return !TextUtils.isEmpty(systemId) && FFC_GROUPS.contains(systemId); |
| } |
| |
| /** |
| * Returns true the URI is a group URI. |
| */ |
| public static boolean isGroupUri(Uri uri) { |
| return uri != null && uri.toString().startsWith(Groups.CONTENT_URI.toString()); |
| } |
| |
| /** |
| * Sort groups alphabetically and in a localized way. |
| */ |
| public static String getGroupsSortOrder() { |
| return Groups.TITLE + " COLLATE LOCALIZED ASC"; |
| } |
| |
| /** |
| * The sum of the last element in counts[] and the last element in positions[] is the total |
| * number of remaining elements in cursor. If count is more than what's in the indexer now, |
| * then we don't need to trim. |
| */ |
| public static boolean needTrimming(int count, int[] counts, int[] positions) { |
| // The sum of the last element in counts[] and the last element in positions[] is |
| // the total number of remaining elements in cursor. If mCount is more than |
| // what's in the indexer now, then we don't need to trim. |
| return positions.length > 0 && counts.length > 0 |
| && count <= (counts[counts.length - 1] + positions[positions.length - 1]); |
| } |
| |
| /** |
| * Update Bundle extras so as to update indexer. |
| */ |
| public static void updateBundle(Bundle bundle, ContactsSectionIndexer indexer, |
| List<Integer> subscripts, String[] sections, int[] counts) { |
| for (int i : subscripts) { |
| final int filteredContact = indexer.getSectionForPosition(i); |
| if (filteredContact < counts.length && filteredContact >= 0) { |
| counts[filteredContact]--; |
| if (counts[filteredContact] == 0) { |
| sections[filteredContact] = ""; |
| } |
| } |
| } |
| final String[] newSections = clearEmptyString(sections); |
| bundle.putStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES, newSections); |
| final int[] newCounts = clearZeros(counts); |
| bundle.putIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS, newCounts); |
| } |
| |
| private static String[] clearEmptyString(String[] strings) { |
| final List<String> list = new ArrayList<>(); |
| for (String s : strings) { |
| if (!TextUtils.isEmpty(s)) { |
| list.add(s); |
| } |
| } |
| return list.toArray(new String[list.size()]); |
| } |
| |
| private static int[] clearZeros(int[] numbers) { |
| final List<Integer> list = new ArrayList<>(); |
| for (int n : numbers) { |
| if (n > 0) { |
| list.add(n); |
| } |
| } |
| final int[] array = new int[list.size()]; |
| for(int i = 0; i < list.size(); i++) { |
| array[i] = list.get(i); |
| } |
| return array; |
| } |
| |
| /** |
| * Stores column ordering for the projection of a query of ContactsContract.Groups |
| */ |
| public static final class GroupsProjection { |
| public final int groupId; |
| public final int title; |
| public final int summaryCount; |
| public final int systemId; |
| public final int accountName; |
| public final int accountType; |
| public final int dataSet; |
| public final int autoAdd; |
| public final int favorites; |
| public final int isReadOnly; |
| public final int deleted; |
| |
| public GroupsProjection(Cursor cursor) { |
| groupId = cursor.getColumnIndex(Groups._ID); |
| title = cursor.getColumnIndex(Groups.TITLE); |
| summaryCount = cursor.getColumnIndex(Groups.SUMMARY_COUNT); |
| systemId = cursor.getColumnIndex(Groups.SYSTEM_ID); |
| accountName = cursor.getColumnIndex(Groups.ACCOUNT_NAME); |
| accountType = cursor.getColumnIndex(Groups.ACCOUNT_TYPE); |
| dataSet = cursor.getColumnIndex(Groups.DATA_SET); |
| autoAdd = cursor.getColumnIndex(Groups.AUTO_ADD); |
| favorites = cursor.getColumnIndex(Groups.FAVORITES); |
| isReadOnly = cursor.getColumnIndex(Groups.GROUP_IS_READ_ONLY); |
| deleted = cursor.getColumnIndex(Groups.DELETED); |
| } |
| |
| public GroupsProjection(String[] projection) { |
| List<String> list = Arrays.asList(projection); |
| groupId = list.indexOf(Groups._ID); |
| title = list.indexOf(Groups.TITLE); |
| summaryCount = list.indexOf(Groups.SUMMARY_COUNT); |
| systemId = list.indexOf(Groups.SYSTEM_ID); |
| accountName = list.indexOf(Groups.ACCOUNT_NAME); |
| accountType = list.indexOf(Groups.ACCOUNT_TYPE); |
| dataSet = list.indexOf(Groups.DATA_SET); |
| autoAdd = list.indexOf(Groups.AUTO_ADD); |
| favorites = list.indexOf(Groups.FAVORITES); |
| isReadOnly = list.indexOf(Groups.GROUP_IS_READ_ONLY); |
| deleted = list.indexOf(Groups.DELETED); |
| } |
| |
| public String getTitle(Cursor cursor) { |
| return cursor.getString(title); |
| } |
| |
| public long getId(Cursor cursor) { |
| return cursor.getLong(groupId); |
| } |
| |
| public String getSystemId(Cursor cursor) { |
| return cursor.getString(systemId); |
| } |
| |
| public int getSummaryCount(Cursor cursor) { |
| return cursor.getInt(summaryCount); |
| } |
| |
| public boolean isEmptyFFCGroup(Cursor cursor) { |
| if (accountType == -1 || isReadOnly == -1 || |
| systemId == -1 || summaryCount == -1) { |
| throw new IllegalArgumentException("Projection is missing required columns"); |
| } |
| return GoogleAccountType.ACCOUNT_TYPE.equals(cursor.getString(accountType)) |
| && cursor.getInt(isReadOnly) != 0 |
| && isSystemIdFFC(cursor.getString(systemId)) |
| && cursor.getInt(summaryCount) <= 0; |
| } |
| } |
| } |