Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 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.content.ContentUris; |
| 20 | import android.content.Context; |
| 21 | import android.content.Intent; |
| 22 | import android.database.Cursor; |
| 23 | import android.net.Uri; |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 24 | import android.os.Bundle; |
| 25 | import android.provider.ContactsContract.Contacts; |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 26 | import android.provider.ContactsContract.Groups; |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 27 | import android.text.TextUtils; |
Walter Jang | 5dde93f | 2016-05-26 13:56:53 -0700 | [diff] [blame] | 28 | import android.widget.ImageView; |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 29 | |
| 30 | import com.android.contacts.GroupListLoader; |
Walter Jang | 4715c04 | 2016-04-06 17:14:23 -0700 | [diff] [blame] | 31 | import com.android.contacts.activities.GroupMembersActivity; |
Walter Jang | 5dde93f | 2016-05-26 13:56:53 -0700 | [diff] [blame] | 32 | import com.android.contacts.common.ContactPhotoManager; |
| 33 | import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 34 | import com.android.contacts.common.list.ContactsSectionIndexer; |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 35 | import com.android.contacts.common.model.account.GoogleAccountType; |
Wenyi Wang | 5289a29 | 2016-06-27 12:42:54 -0700 | [diff] [blame^] | 36 | import com.android.contacts.common.testing.NeededForTesting; |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 37 | import com.google.common.base.Objects; |
| 38 | |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 39 | import java.util.ArrayList; |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 40 | import java.util.Arrays; |
| 41 | import java.util.HashSet; |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 42 | import java.util.List; |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 43 | import java.util.Set; |
| 44 | |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 45 | /** |
| 46 | * Group utility methods. |
| 47 | */ |
Wenyi Wang | 5289a29 | 2016-06-27 12:42:54 -0700 | [diff] [blame^] | 48 | @NeededForTesting |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 49 | public final class GroupUtil { |
| 50 | |
Walter Jang | 0680a31 | 2016-05-18 13:09:01 -0700 | [diff] [blame] | 51 | private static final String LEGACY_CONTACTS_AUTHORITY = "contacts"; |
| 52 | private static final String LEGACY_CONTACTS_URI = "content://contacts/groups"; |
| 53 | |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 54 | // System IDs of FFC groups in Google accounts |
| 55 | private static final Set<String> FFC_GROUPS = |
| 56 | new HashSet(Arrays.asList("Friends", "Family", "Coworkers")); |
| 57 | |
Wenyi Wang | 8607705 | 2016-06-29 18:21:21 -0700 | [diff] [blame] | 58 | public static final String EXTRA_GROUP_NAME = "groupName"; |
| 59 | |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 60 | private GroupUtil() { |
| 61 | } |
| 62 | |
| 63 | /** Returns a {@link GroupListItem} read from the given cursor and position. */ |
| 64 | static GroupListItem getGroupListItem(Cursor cursor, int position) { |
| 65 | if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(position)) { |
| 66 | return null; |
| 67 | } |
| 68 | String accountName = cursor.getString(GroupListLoader.ACCOUNT_NAME); |
| 69 | String accountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE); |
| 70 | String dataSet = cursor.getString(GroupListLoader.DATA_SET); |
| 71 | long groupId = cursor.getLong(GroupListLoader.GROUP_ID); |
| 72 | String title = cursor.getString(GroupListLoader.TITLE); |
| 73 | int memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT); |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 74 | boolean isReadOnly = cursor.getInt(GroupListLoader.IS_READ_ONLY) == 1; |
| 75 | String systemId = cursor.getString(GroupListLoader.SYSTEM_ID); |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 76 | |
| 77 | // Figure out if this is the first group for this account name / account type pair by |
| 78 | // checking the previous entry. This is to determine whether or not we need to display an |
| 79 | // account header in this item. |
| 80 | int previousIndex = position - 1; |
| 81 | boolean isFirstGroupInAccount = true; |
| 82 | if (previousIndex >= 0 && cursor.moveToPosition(previousIndex)) { |
| 83 | String previousGroupAccountName = cursor.getString(GroupListLoader.ACCOUNT_NAME); |
| 84 | String previousGroupAccountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE); |
| 85 | String previousGroupDataSet = cursor.getString(GroupListLoader.DATA_SET); |
| 86 | |
| 87 | if (accountName.equals(previousGroupAccountName) && |
| 88 | accountType.equals(previousGroupAccountType) && |
| 89 | Objects.equal(dataSet, previousGroupDataSet)) { |
| 90 | isFirstGroupInAccount = false; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | return new GroupListItem(accountName, accountType, dataSet, groupId, title, |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 95 | isFirstGroupInAccount, memberCount, isReadOnly, systemId); |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 96 | } |
| 97 | |
Walter Jang | 5dde93f | 2016-05-26 13:56:53 -0700 | [diff] [blame] | 98 | /** |
| 99 | * @param identifier the {@link ContactPhotoManager.DefaultImageRequest#identifier} |
| 100 | * to use for this the group member. |
| 101 | */ |
| 102 | public static void bindPhoto(ContactPhotoManager photoManager, ImageView imageView, |
| 103 | long photoId, Uri photoUri, String displayName, String identifier) { |
| 104 | if (photoId == 0) { |
| 105 | final DefaultImageRequest defaultImageRequest = photoUri == null |
| 106 | ? new DefaultImageRequest(displayName, identifier, |
| 107 | /* circularPhotos */ true) |
| 108 | : null; |
| 109 | photoManager.loadDirectoryPhoto(imageView, photoUri, /* darkTheme */ false, |
| 110 | /* isCircular */ true, defaultImageRequest); |
| 111 | } else { |
| 112 | photoManager.loadThumbnail(imageView, photoId, /* darkTheme */ false, |
| 113 | /* isCircular */ true, /* defaultImageRequest */ null); |
| 114 | } |
| 115 | } |
| 116 | |
Walter Jang | cdbcd88 | 2016-04-02 18:44:12 -0700 | [diff] [blame] | 117 | /** Returns an Intent to create a new group. */ |
| 118 | public static Intent createAddGroupIntent(Context context) { |
Walter Jang | 0680a31 | 2016-05-18 13:09:01 -0700 | [diff] [blame] | 119 | final Intent intent = new Intent(context, GroupMembersActivity.class); |
Walter Jang | cdbcd88 | 2016-04-02 18:44:12 -0700 | [diff] [blame] | 120 | intent.setAction(Intent.ACTION_INSERT); |
| 121 | return intent; |
| 122 | } |
| 123 | |
Walter Jang | 0680a31 | 2016-05-18 13:09:01 -0700 | [diff] [blame] | 124 | /** Returns an Intent to view the details of the group identified by the given ID. */ |
Wenyi Wang | 8607705 | 2016-06-29 18:21:21 -0700 | [diff] [blame] | 125 | public static Intent createViewGroupIntent(Context context, long groupId, String title) { |
Walter Jang | 46f9006 | 2016-04-14 14:49:12 -0700 | [diff] [blame] | 126 | final Intent intent = new Intent(context, GroupMembersActivity.class); |
Walter Jang | 0680a31 | 2016-05-18 13:09:01 -0700 | [diff] [blame] | 127 | intent.setAction(Intent.ACTION_VIEW); |
Walter Jang | 72f9988 | 2016-05-26 09:01:31 -0700 | [diff] [blame] | 128 | intent.setData(ContentUris.withAppendedId(Groups.CONTENT_URI, groupId)); |
Wenyi Wang | 8607705 | 2016-06-29 18:21:21 -0700 | [diff] [blame] | 129 | intent.putExtra(EXTRA_GROUP_NAME, title); |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 130 | return intent; |
| 131 | } |
| 132 | |
Walter Jang | 0680a31 | 2016-05-18 13:09:01 -0700 | [diff] [blame] | 133 | /** |
| 134 | * Converts the given group Uri to the legacy format if the legacy authority was specified |
| 135 | * in the given Uri. |
| 136 | */ |
Walter Jang | 5dde93f | 2016-05-26 13:56:53 -0700 | [diff] [blame] | 137 | // TODO(wjang): |
Walter Jang | 0680a31 | 2016-05-18 13:09:01 -0700 | [diff] [blame] | 138 | public static Uri maybeConvertToLegacyUri(Uri groupUri) { |
| 139 | final String requestAuthority = groupUri.getAuthority(); |
| 140 | if (!LEGACY_CONTACTS_AUTHORITY.equals(requestAuthority)) { |
| 141 | return groupUri; |
| 142 | } |
| 143 | final long groupId = ContentUris.parseId(groupUri); |
| 144 | final Uri legacyContentUri = Uri.parse(LEGACY_CONTACTS_URI); |
| 145 | return ContentUris.withAppendedId(legacyContentUri, groupId); |
| 146 | } |
Wenyi Wang | 3fafbb2 | 2016-06-02 16:56:53 -0700 | [diff] [blame] | 147 | |
| 148 | /** |
| 149 | * Returns true if it's an empty and read-only group of a Google account and the system ID of |
| 150 | * the group is one of "Friends", "Family" and "Coworkers". |
| 151 | */ |
| 152 | public static boolean isEmptyFFCGroup(GroupListItem groupListItem) { |
| 153 | return GoogleAccountType.ACCOUNT_TYPE.equals(groupListItem.getAccountType()) |
| 154 | && groupListItem.isReadOnly() |
| 155 | && isSystemIdFFC(groupListItem.getSystemId()) |
| 156 | && (groupListItem.getMemberCount() <= 0); |
| 157 | } |
| 158 | |
| 159 | private static boolean isSystemIdFFC(String systemId) { |
| 160 | return !TextUtils.isEmpty(systemId) && FFC_GROUPS.contains(systemId); |
| 161 | } |
Wenyi Wang | 9afadde | 2016-06-16 15:10:59 -0700 | [diff] [blame] | 162 | |
| 163 | /** |
| 164 | * Sort groups alphabetically and in a localized way. |
| 165 | */ |
| 166 | public static String getGroupsSortOrder() { |
| 167 | return Groups.TITLE + " COLLATE LOCALIZED ASC"; |
| 168 | } |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 169 | |
| 170 | /** |
| 171 | * The sum of the last element in counts[] and the last element in positions[] is the total |
| 172 | * number of remaining elements in cursor. If count is more than what's in the indexer now, |
| 173 | * then we don't need to trim. |
| 174 | */ |
Wenyi Wang | 5289a29 | 2016-06-27 12:42:54 -0700 | [diff] [blame^] | 175 | @NeededForTesting |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 176 | public static boolean needTrimming(int count, int[] counts, int[] positions) { |
| 177 | // The sum of the last element in counts[] and the last element in positions[] is |
| 178 | // the total number of remaining elements in cursor. If mCount is more than |
| 179 | // what's in the indexer now, then we don't need to trim. |
| 180 | return positions.length > 0 && counts.length > 0 |
| 181 | && count <= (counts[counts.length - 1] + positions[positions.length - 1]); |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Update Bundle extras so as to update indexer. |
| 186 | */ |
Wenyi Wang | 5289a29 | 2016-06-27 12:42:54 -0700 | [diff] [blame^] | 187 | @NeededForTesting |
Wenyi Wang | 38860a5 | 2016-06-24 17:47:34 -0700 | [diff] [blame] | 188 | public static void updateBundle(Bundle bundle, ContactsSectionIndexer indexer, |
| 189 | List<Integer> subscripts, String[] sections, int[] counts) { |
| 190 | for (int i : subscripts) { |
| 191 | final int filteredContact = indexer.getSectionForPosition(i); |
| 192 | if (filteredContact < counts.length && filteredContact >= 0) { |
| 193 | counts[filteredContact]--; |
| 194 | if (counts[filteredContact] == 0) { |
| 195 | sections[filteredContact] = ""; |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | final String[] newSections = clearEmptyString(sections); |
| 200 | bundle.putStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES, newSections); |
| 201 | final int[] newCounts = clearZeros(counts); |
| 202 | bundle.putIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS, newCounts); |
| 203 | } |
| 204 | |
| 205 | private static String[] clearEmptyString(String[] strings) { |
| 206 | final List<String> list = new ArrayList<>(); |
| 207 | for (String s : strings) { |
| 208 | if (!TextUtils.isEmpty(s)) { |
| 209 | list.add(s); |
| 210 | } |
| 211 | } |
| 212 | return list.toArray(new String[list.size()]); |
| 213 | } |
| 214 | |
| 215 | private static int[] clearZeros(int[] numbers) { |
| 216 | final List<Integer> list = new ArrayList<>(); |
| 217 | for (int n : numbers) { |
| 218 | if (n > 0) { |
| 219 | list.add(n); |
| 220 | } |
| 221 | } |
| 222 | final int[] array = new int[list.size()]; |
| 223 | for(int i = 0; i < list.size(); i++) { |
| 224 | array[i] = list.get(i); |
| 225 | } |
| 226 | return array; |
| 227 | } |
Walter Jang | 758f565 | 2016-04-05 09:29:56 -0700 | [diff] [blame] | 228 | } |