Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 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 | |
Gary Mai | 0a49afa | 2016-12-05 15:53:58 -0800 | [diff] [blame] | 17 | package com.android.contacts; |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 18 | |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 19 | import android.content.Context; |
Chiao Cheng | ce14263 | 2012-11-14 18:26:25 -0800 | [diff] [blame] | 20 | import android.content.Intent; |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 21 | import android.graphics.Rect; |
Chiao Cheng | ce14263 | 2012-11-14 18:26:25 -0800 | [diff] [blame] | 22 | import android.net.Uri; |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 23 | import android.provider.ContactsContract; |
| 24 | import android.telephony.PhoneNumberUtils; |
| 25 | import android.text.TextUtils; |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 26 | import android.view.View; |
| 27 | import android.widget.TextView; |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 28 | |
Gary Mai | 69c182a | 2016-12-05 13:07:03 -0800 | [diff] [blame] | 29 | import com.android.contacts.model.account.AccountType; |
Chiao Cheng | ce14263 | 2012-11-14 18:26:25 -0800 | [diff] [blame] | 30 | |
Gary Mai | 0a49afa | 2016-12-05 15:53:58 -0800 | [diff] [blame] | 31 | import com.google.i18n.phonenumbers.NumberParseException; |
| 32 | import com.google.i18n.phonenumbers.PhoneNumberUtil; |
| 33 | |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 34 | /** |
| 35 | * Shared static contact utility methods. |
| 36 | */ |
| 37 | public class MoreContactUtils { |
| 38 | |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 39 | private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT); |
| 40 | |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 41 | /** |
| 42 | * Returns true if two data with mimetypes which represent values in contact entries are |
| 43 | * considered equal for collapsing in the GUI. For caller-id, use |
| 44 | * {@link android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} |
| 45 | * instead |
| 46 | */ |
| 47 | public static boolean shouldCollapse(CharSequence mimetype1, CharSequence data1, |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 48 | CharSequence mimetype2, CharSequence data2) { |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 49 | // different mimetypes? don't collapse |
| 50 | if (!TextUtils.equals(mimetype1, mimetype2)) return false; |
| 51 | |
| 52 | // exact same string? good, bail out early |
| 53 | if (TextUtils.equals(data1, data2)) return true; |
| 54 | |
| 55 | // so if either is null, these two must be different |
| 56 | if (data1 == null || data2 == null) return false; |
| 57 | |
| 58 | // if this is not about phone numbers, we know this is not a match (of course, some |
| 59 | // mimetypes could have more sophisticated matching is the future, e.g. addresses) |
| 60 | if (!TextUtils.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, |
| 61 | mimetype1)) { |
| 62 | return false; |
| 63 | } |
| 64 | |
| 65 | return shouldCollapsePhoneNumbers(data1.toString(), data2.toString()); |
| 66 | } |
| 67 | |
Paul Soulos | a69518c | 2014-07-28 14:12:23 -0700 | [diff] [blame] | 68 | // TODO: Move this to PhoneDataItem.shouldCollapse override |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 69 | private static boolean shouldCollapsePhoneNumbers(String number1, String number2) { |
Brian Attwell | ab9f821 | 2015-05-05 14:34:50 -0700 | [diff] [blame] | 70 | // Work around to address b/20724444. We want to distinguish between #555, *555 and 555. |
| 71 | // This makes no attempt to distinguish between 555 and 55*5, since 55*5 is an improbable |
| 72 | // number. PhoneNumberUtil already distinguishes between 555 and 55#5. |
| 73 | if (number1.contains("#") != number2.contains("#") |
| 74 | || number1.contains("*") != number2.contains("*")) { |
| 75 | return false; |
| 76 | } |
| 77 | |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 78 | // Now do the full phone number thing. split into parts, separated by waiting symbol |
| 79 | // and compare them individually |
| 80 | final String[] dataParts1 = number1.split(WAIT_SYMBOL_AS_STRING); |
| 81 | final String[] dataParts2 = number2.split(WAIT_SYMBOL_AS_STRING); |
| 82 | if (dataParts1.length != dataParts2.length) return false; |
| 83 | final PhoneNumberUtil util = PhoneNumberUtil.getInstance(); |
| 84 | for (int i = 0; i < dataParts1.length; i++) { |
| 85 | // Match phone numbers represented by keypad letters, in which case prefer the |
| 86 | // phone number with letters. |
| 87 | final String dataPart1 = PhoneNumberUtils.convertKeypadLettersToDigits(dataParts1[i]); |
| 88 | final String dataPart2 = dataParts2[i]; |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 89 | |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 90 | // substrings equal? shortcut, don't parse |
| 91 | if (TextUtils.equals(dataPart1, dataPart2)) continue; |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 92 | |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 93 | // do a full parse of the numbers |
Chiao Cheng | 88331e3 | 2013-04-16 16:45:48 -0700 | [diff] [blame] | 94 | final PhoneNumberUtil.MatchType result = util.isNumberMatch(dataPart1, dataPart2); |
| 95 | switch (result) { |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 96 | case NOT_A_NUMBER: |
| 97 | // don't understand the numbers? let's play it safe |
| 98 | return false; |
| 99 | case NO_MATCH: |
| 100 | return false; |
| 101 | case EXACT_MATCH: |
| 102 | break; |
| 103 | case NSN_MATCH: |
| 104 | try { |
| 105 | // For NANP phone numbers, match when one has +1 and the other does not. |
| 106 | // In this case, prefer the +1 version. |
| 107 | if (util.parse(dataPart1, null).getCountryCode() == 1) { |
Chiao Cheng | 88331e3 | 2013-04-16 16:45:48 -0700 | [diff] [blame] | 108 | // At this point, the numbers can be either case 1 or 2 below.... |
| 109 | // |
| 110 | // case 1) |
| 111 | // +14155551212 <--- country code 1 |
| 112 | // 14155551212 <--- 1 is trunk prefix, not country code |
| 113 | // |
| 114 | // and |
| 115 | // |
| 116 | // case 2) |
| 117 | // +14155551212 |
| 118 | // 4155551212 |
| 119 | // |
| 120 | // From b/7519057, case 2 needs to be equal. But also that bug, case 3 |
| 121 | // below should not be equal. |
| 122 | // |
| 123 | // case 3) |
| 124 | // 14155551212 |
| 125 | // 4155551212 |
| 126 | // |
| 127 | // So in order to make sure transitive equality is valid, case 1 cannot |
| 128 | // be equal. Otherwise, transitive equality breaks and the following |
| 129 | // would all be collapsed: |
| 130 | // 4155551212 | |
| 131 | // 14155551212 |----> +14155551212 |
| 132 | // +14155551212 | |
| 133 | // |
| 134 | // With transitive equality, the collapsed values should be: |
| 135 | // 4155551212 | 14155551212 |
| 136 | // 14155551212 |----> +14155551212 |
| 137 | // +14155551212 | |
| 138 | |
| 139 | // Distinguish between case 1 and 2 by checking for trunk prefix '1' |
| 140 | // at the start of number 2. |
| 141 | if (dataPart2.trim().charAt(0) == '1') { |
| 142 | // case 1 |
| 143 | return false; |
| 144 | } |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 145 | break; |
| 146 | } |
| 147 | } catch (NumberParseException e) { |
Chiao Cheng | 88331e3 | 2013-04-16 16:45:48 -0700 | [diff] [blame] | 148 | // This is the case where the first number does not have a country code. |
| 149 | // examples: |
| 150 | // (123) 456-7890 & 123-456-7890 (collapse) |
| 151 | // 0049 (8092) 1234 & +49/80921234 (unit test says do not collapse) |
| 152 | |
| 153 | // Check the second number. If it also does not have a country code, then |
| 154 | // we should collapse. If it has a country code, then it's a different |
| 155 | // number and we should not collapse (this conclusion is based on an |
| 156 | // existing unit test). |
| 157 | try { |
| 158 | util.parse(dataPart2, null); |
| 159 | } catch (NumberParseException e2) { |
| 160 | // Number 2 also does not have a country. Collapse. |
| 161 | break; |
| 162 | } |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 163 | } |
| 164 | return false; |
| 165 | case SHORT_NSN_MATCH: |
| 166 | return false; |
| 167 | default: |
| 168 | throw new IllegalStateException("Unknown result value from phone number " + |
| 169 | "library"); |
| 170 | } |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 171 | } |
Jay Shrauner | 34ad2aa | 2013-01-08 11:41:55 -0800 | [diff] [blame] | 172 | return true; |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 173 | } |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 174 | |
| 175 | /** |
| 176 | * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates |
| 177 | * that are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to |
| 178 | * how the target {@link android.graphics.Rect} is calculated in |
| 179 | * {@link android.provider.ContactsContract.QuickContact#showQuickContact}. |
| 180 | */ |
Yorke Lee | 22426f9 | 2013-10-24 13:53:24 -0700 | [diff] [blame] | 181 | public static Rect getTargetRectFromView(View view) { |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 182 | final int[] pos = new int[2]; |
| 183 | view.getLocationOnScreen(pos); |
| 184 | |
| 185 | final Rect rect = new Rect(); |
Yorke Lee | 22426f9 | 2013-10-24 13:53:24 -0700 | [diff] [blame] | 186 | rect.left = pos[0]; |
| 187 | rect.top = pos[1]; |
| 188 | rect.right = pos[0] + view.getWidth(); |
| 189 | rect.bottom = pos[1] + view.getHeight(); |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 190 | return rect; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Returns a header view based on the R.layout.list_separator, where the |
| 195 | * containing {@link android.widget.TextView} is set using the given textResourceId. |
| 196 | */ |
Brian Attwell | 8cb7b46 | 2014-06-25 13:54:49 -0700 | [diff] [blame] | 197 | public static TextView createHeaderView(Context context, int textResourceId) { |
| 198 | final TextView textView = (TextView) View.inflate(context, R.layout.list_separator, null); |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 199 | textView.setText(context.getString(textResourceId)); |
Brian Attwell | 8cb7b46 | 2014-06-25 13:54:49 -0700 | [diff] [blame] | 200 | return textView; |
Chiao Cheng | 87a36dc | 2012-11-07 18:20:17 -0800 | [diff] [blame] | 201 | } |
Chiao Cheng | ce14263 | 2012-11-14 18:26:25 -0800 | [diff] [blame] | 202 | |
| 203 | /** |
Brian Attwell | 8cb7b46 | 2014-06-25 13:54:49 -0700 | [diff] [blame] | 204 | * Set the top padding on the header view dynamically, based on whether the header is in |
| 205 | * the first row or not. |
| 206 | */ |
| 207 | public static void setHeaderViewBottomPadding(Context context, TextView textView, |
| 208 | boolean isFirstRow) { |
| 209 | final int topPadding; |
| 210 | if (isFirstRow) { |
| 211 | topPadding = (int) context.getResources().getDimension( |
| 212 | R.dimen.frequently_contacted_title_top_margin_when_first_row); |
| 213 | } else { |
| 214 | topPadding = (int) context.getResources().getDimension( |
| 215 | R.dimen.frequently_contacted_title_top_margin); |
| 216 | } |
| 217 | textView.setPaddingRelative(textView.getPaddingStart(), topPadding, |
| 218 | textView.getPaddingEnd(), textView.getPaddingBottom()); |
| 219 | } |
| 220 | |
| 221 | |
| 222 | /** |
Chiao Cheng | ce14263 | 2012-11-14 18:26:25 -0800 | [diff] [blame] | 223 | * Returns the intent to launch for the given invitable account type and contact lookup URI. |
| 224 | * This will return null if the account type is not invitable (i.e. there is no |
| 225 | * {@link AccountType#getInviteContactActivityClassName()} or |
| 226 | * {@link AccountType#syncAdapterPackageName}). |
| 227 | */ |
| 228 | public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) { |
| 229 | String syncAdapterPackageName = accountType.syncAdapterPackageName; |
| 230 | String className = accountType.getInviteContactActivityClassName(); |
| 231 | if (TextUtils.isEmpty(syncAdapterPackageName) || TextUtils.isEmpty(className)) { |
| 232 | return null; |
| 233 | } |
| 234 | Intent intent = new Intent(); |
| 235 | intent.setClassName(syncAdapterPackageName, className); |
| 236 | |
| 237 | intent.setAction(ContactsContract.Intents.INVITE_CONTACT); |
| 238 | |
| 239 | // Data is the lookup URI. |
| 240 | intent.setData(lookupUri); |
| 241 | return intent; |
| 242 | } |
Chiao Cheng | 3298464 | 2012-10-24 15:19:31 -0700 | [diff] [blame] | 243 | } |