blob: 7f000ec6346690e68389bb7584bccbdff30bda5b [file] [log] [blame]
Chiao Cheng32984642012-10-24 15:19:31 -07001/*
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 Mai0a49afa2016-12-05 15:53:58 -080017package com.android.contacts;
Jay Shrauner34ad2aa2013-01-08 11:41:55 -080018
Chiao Cheng87a36dc2012-11-07 18:20:17 -080019import android.content.Context;
Chiao Chengce142632012-11-14 18:26:25 -080020import android.content.Intent;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080021import android.graphics.Rect;
Chiao Chengce142632012-11-14 18:26:25 -080022import android.net.Uri;
Chiao Cheng32984642012-10-24 15:19:31 -070023import android.provider.ContactsContract;
24import android.telephony.PhoneNumberUtils;
25import android.text.TextUtils;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080026import android.view.View;
27import android.widget.TextView;
Chiao Cheng32984642012-10-24 15:19:31 -070028
Gary Mai69c182a2016-12-05 13:07:03 -080029import com.android.contacts.model.account.AccountType;
Chiao Chengce142632012-11-14 18:26:25 -080030
Gary Mai0a49afa2016-12-05 15:53:58 -080031import com.google.i18n.phonenumbers.NumberParseException;
32import com.google.i18n.phonenumbers.PhoneNumberUtil;
33
Chiao Cheng32984642012-10-24 15:19:31 -070034/**
35 * Shared static contact utility methods.
36 */
37public class MoreContactUtils {
38
Jay Shrauner34ad2aa2013-01-08 11:41:55 -080039 private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
40
Chiao Cheng32984642012-10-24 15:19:31 -070041 /**
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 Shrauner34ad2aa2013-01-08 11:41:55 -080048 CharSequence mimetype2, CharSequence data2) {
Chiao Cheng32984642012-10-24 15:19:31 -070049 // 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 Soulosa69518c2014-07-28 14:12:23 -070068 // TODO: Move this to PhoneDataItem.shouldCollapse override
Jay Shrauner34ad2aa2013-01-08 11:41:55 -080069 private static boolean shouldCollapsePhoneNumbers(String number1, String number2) {
Brian Attwellab9f8212015-05-05 14:34:50 -070070 // 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 Shrauner34ad2aa2013-01-08 11:41:55 -080078 // 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 Cheng32984642012-10-24 15:19:31 -070089
Jay Shrauner34ad2aa2013-01-08 11:41:55 -080090 // substrings equal? shortcut, don't parse
91 if (TextUtils.equals(dataPart1, dataPart2)) continue;
Chiao Cheng32984642012-10-24 15:19:31 -070092
Jay Shrauner34ad2aa2013-01-08 11:41:55 -080093 // do a full parse of the numbers
Chiao Cheng88331e32013-04-16 16:45:48 -070094 final PhoneNumberUtil.MatchType result = util.isNumberMatch(dataPart1, dataPart2);
95 switch (result) {
Jay Shrauner34ad2aa2013-01-08 11:41:55 -080096 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 Cheng88331e32013-04-16 16:45:48 -0700108 // 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 Shrauner34ad2aa2013-01-08 11:41:55 -0800145 break;
146 }
147 } catch (NumberParseException e) {
Chiao Cheng88331e32013-04-16 16:45:48 -0700148 // 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 Shrauner34ad2aa2013-01-08 11:41:55 -0800163 }
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 Cheng32984642012-10-24 15:19:31 -0700171 }
Jay Shrauner34ad2aa2013-01-08 11:41:55 -0800172 return true;
Chiao Cheng32984642012-10-24 15:19:31 -0700173 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800174
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 Lee22426f92013-10-24 13:53:24 -0700181 public static Rect getTargetRectFromView(View view) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800182 final int[] pos = new int[2];
183 view.getLocationOnScreen(pos);
184
185 final Rect rect = new Rect();
Yorke Lee22426f92013-10-24 13:53:24 -0700186 rect.left = pos[0];
187 rect.top = pos[1];
188 rect.right = pos[0] + view.getWidth();
189 rect.bottom = pos[1] + view.getHeight();
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800190 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 Attwell8cb7b462014-06-25 13:54:49 -0700197 public static TextView createHeaderView(Context context, int textResourceId) {
198 final TextView textView = (TextView) View.inflate(context, R.layout.list_separator, null);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800199 textView.setText(context.getString(textResourceId));
Brian Attwell8cb7b462014-06-25 13:54:49 -0700200 return textView;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800201 }
Chiao Chengce142632012-11-14 18:26:25 -0800202
203 /**
Brian Attwell8cb7b462014-06-25 13:54:49 -0700204 * 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 Chengce142632012-11-14 18:26:25 -0800223 * 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 Cheng32984642012-10-24 15:19:31 -0700243}