blob: ec9940836afea584a1281d5062555a5cec5be9e7 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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
17package android.telephony;
18
Pengquan Meng0879c902018-02-01 13:39:44 -080019import android.annotation.IntDef;
Hall Liud2f962a2019-10-31 15:17:58 -070020import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.SystemApi;
Taesu Leec180e462019-05-28 19:05:06 +090023import android.annotation.TestApi;
Artur Satayev54af4fc2019-12-10 17:47:56 +000024import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Context;
26import android.content.Intent;
Taesu Leec180e462019-05-28 19:05:06 +090027import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.database.Cursor;
29import android.net.Uri;
Jordan Liu360d5672016-11-09 13:23:42 -080030import android.os.PersistableBundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.provider.Contacts;
Jeff Sharkeyf28ca902009-08-08 18:25:45 -070032import android.provider.ContactsContract;
Inseob Kima9d6cd42019-11-08 15:06:37 +090033import android.sysprop.TelephonyProperties;
Tyler Gunn825da232016-05-10 20:04:27 -070034import android.telecom.PhoneAccount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.text.Editable;
Ihab Awad0855e7a2014-12-05 13:38:11 -080036import android.text.Spannable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.text.SpannableStringBuilder;
38import android.text.TextUtils;
Ihab Awad0855e7a2014-12-05 13:38:11 -080039import android.text.style.TtsSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.util.SparseIntArray;
41
Hall Liud2f962a2019-10-31 15:17:58 -070042import com.android.i18n.phonenumbers.NumberParseException;
43import com.android.i18n.phonenumbers.PhoneNumberUtil;
44import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
45import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
Meng Wang1dbea2f2020-01-30 12:25:08 -080046import com.android.telephony.Rlog;
Tang@Motorola.com18e7b982009-08-03 18:06:04 -050047
Pengquan Meng0879c902018-02-01 13:39:44 -080048import java.lang.annotation.Retention;
49import java.lang.annotation.RetentionPolicy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import java.util.Locale;
51import java.util.regex.Matcher;
52import java.util.regex.Pattern;
53
54/**
55 * Various utilities for dealing with phone number strings.
56 */
Pengquan Meng0879c902018-02-01 13:39:44 -080057public class PhoneNumberUtils {
58 /** {@hide} */
59 @IntDef(prefix = "BCD_EXTENDED_TYPE_", value = {
60 BCD_EXTENDED_TYPE_EF_ADN,
61 BCD_EXTENDED_TYPE_CALLED_PARTY,
62 })
63 @Retention(RetentionPolicy.SOURCE)
64 public @interface BcdExtendType {}
65
66 /*
67 * The BCD extended type used to determine the extended char for the digit which is greater than
68 * 9.
69 *
70 * see TS 51.011 section 10.5.1 EF_ADN(Abbreviated dialling numbers)
71 */
72 public static final int BCD_EXTENDED_TYPE_EF_ADN = 1;
73
74 /*
75 * The BCD extended type used to determine the extended char for the digit which is greater than
76 * 9.
77 *
78 * see TS 24.008 section 10.5.4.7 Called party BCD number
79 */
80 public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2;
81
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 /*
83 * Special characters
84 *
85 * (See "What is a phone number?" doc)
86 * 'p' --- GSM pause character, same as comma
87 * 'n' --- GSM wild character
88 * 'w' --- GSM wait character
89 */
90 public static final char PAUSE = ',';
91 public static final char WAIT = ';';
92 public static final char WILD = 'N';
93
94 /*
Babis Triantafyllouadff0ad2010-04-08 08:19:39 +020095 * Calling Line Identification Restriction (CLIR)
96 */
Jake Hambye333c822012-01-13 13:14:39 -080097 private static final String CLIR_ON = "*31#";
98 private static final String CLIR_OFF = "#31#";
Babis Triantafyllouadff0ad2010-04-08 08:19:39 +020099
100 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 * TOA = TON + NPI
102 * See TS 24.008 section 10.5.4.7 for details.
103 * These are the only really useful TOA values
104 */
105 public static final int TOA_International = 0x91;
106 public static final int TOA_Unknown = 0x81;
107
Tang@Motorola.com18e7b982009-08-03 18:06:04 -0500108 static final String LOG_TAG = "PhoneNumberUtils";
109 private static final boolean DBG = false;
110
Erika Avenberg0be6d7c2014-08-30 12:00:47 +0200111 private static final String BCD_EF_ADN_EXTENDED = "*#,N;";
112 private static final String BCD_CALLED_PARTY_EXTENDED = "*#abc";
113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 /*
115 * global-phone-number = ["+"] 1*( DIGIT / written-sep )
116 * written-sep = ("-"/".")
117 */
118 private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN =
119 Pattern.compile("[\\+]?[0-9.-]+");
120
121 /** True if c is ISO-LATIN characters 0-9 */
122 public static boolean
123 isISODigit (char c) {
124 return c >= '0' && c <= '9';
125 }
126
127 /** True if c is ISO-LATIN characters 0-9, *, # */
128 public final static boolean
129 is12Key(char c) {
130 return (c >= '0' && c <= '9') || c == '*' || c == '#';
131 }
132
133 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */
134 public final static boolean
135 isDialable(char c) {
136 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD;
137 }
138
139 /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */
140 public final static boolean
141 isReallyDialable(char c) {
142 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+';
143 }
144
145 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */
146 public final static boolean
147 isNonSeparator(char c) {
148 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'
149 || c == WILD || c == WAIT || c == PAUSE;
150 }
151
152 /** This any anything to the right of this char is part of the
153 * post-dial string (eg this is PAUSE or WAIT)
154 */
155 public final static boolean
156 isStartsPostDial (char c) {
157 return c == PAUSE || c == WAIT;
158 }
159
inshik4eb45cc2011-08-12 13:52:46 +0900160 private static boolean
161 isPause (char c){
162 return c == 'p'||c == 'P';
163 }
164
165 private static boolean
166 isToneWait (char c){
167 return c == 'w'||c == 'W';
168 }
169
Taesu Leec180e462019-05-28 19:05:06 +0900170 private static int sMinMatch = 0;
171
172 private static int getMinMatch() {
173 if (sMinMatch == 0) {
174 sMinMatch = Resources.getSystem().getInteger(
175 com.android.internal.R.integer.config_phonenumber_compare_min_match);
176 }
177 return sMinMatch;
178 }
179
180 /**
181 * A Test API to get current sMinMatch.
182 * @hide
183 */
184 @TestApi
185 public static int getMinMatchForTest() {
186 return getMinMatch();
187 }
188
189 /**
190 * A Test API to set sMinMatch.
191 * @hide
192 */
193 @TestApi
194 public static void setMinMatchForTest(int minMatch) {
195 sMinMatch = minMatch;
196 }
inshik4eb45cc2011-08-12 13:52:46 +0900197
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700198 /** Returns true if ch is not dialable or alpha char */
199 private static boolean isSeparator(char ch) {
200 return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'));
201 }
202
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 /** Extracts the phone number from an Intent.
204 *
205 * @param intent the intent to get the number of
206 * @param context a context to use for database access
207 *
208 * @return the phone number that would be called by the intent, or
209 * <code>null</code> if the number cannot be found.
210 */
211 public static String getNumberFromIntent(Intent intent, Context context) {
212 String number = null;
213
214 Uri uri = intent.getData();
Magnus Karlsson22270672013-02-06 11:49:24 +0100215
216 if (uri == null) {
217 return null;
218 }
219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 String scheme = uri.getScheme();
sung_jaeed91c3a2019-08-13 16:43:27 +0900221 if (scheme == null) {
222 return null;
223 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224
Chung-yih Wang6f0f8702010-09-08 13:15:55 +0800225 if (scheme.equals("tel") || scheme.equals("sip")) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 return uri.getSchemeSpecificPart();
227 }
228
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 if (context == null) {
230 return null;
231 }
232
233 String type = intent.resolveType(context);
Jeff Sharkeyf28ca902009-08-08 18:25:45 -0700234 String phoneColumn = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235
Jeff Sharkeyf28ca902009-08-08 18:25:45 -0700236 // Correctly read out the phone entry based on requested provider
237 final String authority = uri.getAuthority();
238 if (Contacts.AUTHORITY.equals(authority)) {
239 phoneColumn = Contacts.People.Phones.NUMBER;
240 } else if (ContactsContract.AUTHORITY.equals(authority)) {
241 phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER;
242 }
243
Roshan Pius93018a42015-07-13 12:57:40 -0700244 Cursor c = null;
245 try {
246 c = context.getContentResolver().query(uri, new String[] { phoneColumn },
247 null, null, null);
248 if (c != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 if (c.moveToFirst()) {
Jeff Sharkeyf28ca902009-08-08 18:25:45 -0700250 number = c.getString(c.getColumnIndex(phoneColumn));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 }
Roshan Pius93018a42015-07-13 12:57:40 -0700252 }
253 } catch (RuntimeException e) {
254 Rlog.e(LOG_TAG, "Error getting phone number.", e);
255 } finally {
256 if (c != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 c.close();
258 }
259 }
260
261 return number;
262 }
263
264 /** Extracts the network address portion and canonicalizes
265 * (filters out separators.)
266 * Network address portion is everything up to DTMF control digit
267 * separators (pause or wait), but without non-dialable characters.
268 *
269 * Please note that the GSM wild character is allowed in the result.
270 * This must be resolved before dialing.
271 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 * Returns null if phoneNumber == null
273 */
274 public static String
275 extractNetworkPortion(String phoneNumber) {
276 if (phoneNumber == null) {
277 return null;
278 }
279
280 int len = phoneNumber.length();
281 StringBuilder ret = new StringBuilder(len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282
283 for (int i = 0; i < len; i++) {
284 char c = phoneNumber.charAt(i);
Jake Hambye333c822012-01-13 13:14:39 -0800285 // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
286 int digit = Character.digit(c, 10);
287 if (digit != -1) {
288 ret.append(digit);
289 } else if (c == '+') {
290 // Allow '+' as first character or after CLIR MMI prefix
291 String prefix = ret.toString();
292 if (prefix.length() == 0 || prefix.equals(CLIR_ON) || prefix.equals(CLIR_OFF)) {
293 ret.append(c);
294 }
295 } else if (isDialable(c)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 ret.append(c);
297 } else if (isStartsPostDial (c)) {
298 break;
299 }
300 }
301
302 return ret.toString();
303 }
304
305 /**
Tammo Spalink9e534152009-10-19 14:10:47 +0800306 * Extracts the network address portion and canonicalize.
307 *
308 * This function is equivalent to extractNetworkPortion(), except
309 * for allowing the PLUS character to occur at arbitrary positions
310 * in the address portion, not just the first position.
311 *
312 * @hide
313 */
Mathew Inwooda8382062018-08-16 17:01:12 +0100314 @UnsupportedAppUsage
Tammo Spalink9e534152009-10-19 14:10:47 +0800315 public static String extractNetworkPortionAlt(String phoneNumber) {
316 if (phoneNumber == null) {
317 return null;
318 }
319
320 int len = phoneNumber.length();
321 StringBuilder ret = new StringBuilder(len);
322 boolean haveSeenPlus = false;
323
324 for (int i = 0; i < len; i++) {
325 char c = phoneNumber.charAt(i);
326 if (c == '+') {
327 if (haveSeenPlus) {
328 continue;
329 }
330 haveSeenPlus = true;
331 }
332 if (isDialable(c)) {
333 ret.append(c);
334 } else if (isStartsPostDial (c)) {
335 break;
336 }
337 }
338
339 return ret.toString();
340 }
341
342 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 * Strips separators from a phone number string.
344 * @param phoneNumber phone number to strip.
345 * @return phone string stripped of separators.
346 */
347 public static String stripSeparators(String phoneNumber) {
348 if (phoneNumber == null) {
349 return null;
350 }
351 int len = phoneNumber.length();
352 StringBuilder ret = new StringBuilder(len);
353
354 for (int i = 0; i < len; i++) {
355 char c = phoneNumber.charAt(i);
Jake Hambye333c822012-01-13 13:14:39 -0800356 // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
357 int digit = Character.digit(c, 10);
358 if (digit != -1) {
359 ret.append(digit);
360 } else if (isNonSeparator(c)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800361 ret.append(c);
362 }
363 }
364
365 return ret.toString();
366 }
367
inshik4eb45cc2011-08-12 13:52:46 +0900368 /**
Daisuke Miyakawa510db8f2012-02-14 19:11:13 -0800369 * Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will
370 * become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become
371 * 18004664411).
372 *
373 * @see #convertKeypadLettersToDigits(String)
374 * @see #stripSeparators(String)
375 *
376 * @hide
377 */
378 public static String convertAndStrip(String phoneNumber) {
379 return stripSeparators(convertKeypadLettersToDigits(phoneNumber));
380 }
381
382 /**
inshik4eb45cc2011-08-12 13:52:46 +0900383 * Converts pause and tonewait pause characters
384 * to Android representation.
385 * RFC 3601 says pause is 'p' and tonewait is 'w'.
386 * @hide
387 */
Mathew Inwooda8382062018-08-16 17:01:12 +0100388 @UnsupportedAppUsage
inshik4eb45cc2011-08-12 13:52:46 +0900389 public static String convertPreDial(String phoneNumber) {
390 if (phoneNumber == null) {
391 return null;
392 }
393 int len = phoneNumber.length();
394 StringBuilder ret = new StringBuilder(len);
395
396 for (int i = 0; i < len; i++) {
397 char c = phoneNumber.charAt(i);
398
399 if (isPause(c)) {
400 c = PAUSE;
401 } else if (isToneWait(c)) {
402 c = WAIT;
403 }
404 ret.append(c);
405 }
406 return ret.toString();
407 }
408
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 /** or -1 if both are negative */
410 static private int
411 minPositive (int a, int b) {
412 if (a >= 0 && b >= 0) {
413 return (a < b) ? a : b;
414 } else if (a >= 0) { /* && b < 0 */
415 return a;
416 } else if (b >= 0) { /* && a < 0 */
417 return b;
418 } else { /* a < 0 && b < 0 */
419 return -1;
420 }
421 }
422
Tang@Motorola.com18e7b982009-08-03 18:06:04 -0500423 private static void log(String msg) {
Wink Saville22b1e802012-12-07 10:26:41 -0800424 Rlog.d(LOG_TAG, msg);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -0500425 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 /** index of the last character of the network portion
427 * (eg anything after is a post-dial string)
428 */
429 static private int
430 indexOfLastNetworkChar(String a) {
431 int pIndex, wIndex;
432 int origLength;
433 int trimIndex;
434
435 origLength = a.length();
436
437 pIndex = a.indexOf(PAUSE);
438 wIndex = a.indexOf(WAIT);
439
440 trimIndex = minPositive(pIndex, wIndex);
441
442 if (trimIndex < 0) {
443 return origLength - 1;
444 } else {
445 return trimIndex - 1;
446 }
447 }
448
449 /**
450 * Extracts the post-dial sequence of DTMF control digits, pauses, and
451 * waits. Strips separators. This string may be empty, but will not be null
452 * unless phoneNumber == null.
453 *
454 * Returns null if phoneNumber == null
455 */
456
457 public static String
458 extractPostDialPortion(String phoneNumber) {
459 if (phoneNumber == null) return null;
460
461 int trimIndex;
462 StringBuilder ret = new StringBuilder();
463
464 trimIndex = indexOfLastNetworkChar (phoneNumber);
465
466 for (int i = trimIndex + 1, s = phoneNumber.length()
467 ; i < s; i++
468 ) {
469 char c = phoneNumber.charAt(i);
470 if (isNonSeparator(c)) {
471 ret.append(c);
472 }
473 }
474
475 return ret.toString();
476 }
477
478 /**
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700479 * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes.
480 */
481 public static boolean compare(String a, String b) {
482 // We've used loose comparation at least Eclair, which may change in the future.
Evan Millardb1f4992009-09-28 17:15:55 -0700483
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700484 return compare(a, b, false);
485 }
486
487 /**
Evan Millardb1f4992009-09-28 17:15:55 -0700488 * Compare phone numbers a and b, and return true if they're identical
489 * enough for caller ID purposes. Checks a resource to determine whether
490 * to use a strict or loose comparison algorithm.
491 */
492 public static boolean compare(Context context, String a, String b) {
493 boolean useStrict = context.getResources().getBoolean(
494 com.android.internal.R.bool.config_use_strict_phone_number_comparation);
495 return compare(a, b, useStrict);
496 }
497
498 /**
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700499 * @hide only for testing.
500 */
Mathew Inwooda8382062018-08-16 17:01:12 +0100501 @UnsupportedAppUsage
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700502 public static boolean compare(String a, String b, boolean useStrictComparation) {
503 return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b));
504 }
505
506 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 * Compare phone numbers a and b, return true if they're identical
508 * enough for caller ID purposes.
509 *
510 * - Compares from right to left
Taesu Leec180e462019-05-28 19:05:06 +0900511 * - requires minimum characters to match
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 * - handles common trunk prefixes and international prefixes
513 * (basically, everything except the Russian trunk prefix)
514 *
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700515 * Note that this method does not return false even when the two phone numbers
516 * are not exactly same; rather; we can call this method "similar()", not "equals()".
517 *
518 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 */
Mathew Inwooda8382062018-08-16 17:01:12 +0100520 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 public static boolean
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700522 compareLoosely(String a, String b) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 int ia, ib;
524 int matched;
Wei Huangfd7b4f12009-10-22 18:03:48 -0700525 int numNonDialableCharsInA = 0;
526 int numNonDialableCharsInB = 0;
Taesu Leec180e462019-05-28 19:05:06 +0900527 int minMatch = getMinMatch();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528
529 if (a == null || b == null) return a == b;
530
531 if (a.length() == 0 || b.length() == 0) {
532 return false;
533 }
534
535 ia = indexOfLastNetworkChar (a);
536 ib = indexOfLastNetworkChar (b);
537 matched = 0;
538
539 while (ia >= 0 && ib >=0) {
540 char ca, cb;
541 boolean skipCmp = false;
542
543 ca = a.charAt(ia);
544
545 if (!isDialable(ca)) {
546 ia--;
547 skipCmp = true;
Wei Huangfd7b4f12009-10-22 18:03:48 -0700548 numNonDialableCharsInA++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 }
550
551 cb = b.charAt(ib);
552
553 if (!isDialable(cb)) {
554 ib--;
555 skipCmp = true;
Wei Huangfd7b4f12009-10-22 18:03:48 -0700556 numNonDialableCharsInB++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 }
558
559 if (!skipCmp) {
560 if (cb != ca && ca != WILD && cb != WILD) {
561 break;
562 }
563 ia--; ib--; matched++;
564 }
565 }
566
Taesu Leec180e462019-05-28 19:05:06 +0900567 if (matched < minMatch) {
Wei Huangfd7b4f12009-10-22 18:03:48 -0700568 int effectiveALen = a.length() - numNonDialableCharsInA;
569 int effectiveBLen = b.length() - numNonDialableCharsInB;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570
Wei Huangfd7b4f12009-10-22 18:03:48 -0700571
Taesu Leec180e462019-05-28 19:05:06 +0900572 // if the number of dialable chars in a and b match, but the matched chars < minMatch,
Wei Huangfd7b4f12009-10-22 18:03:48 -0700573 // treat them as equal (i.e. 404-04 and 40404)
574 if (effectiveALen == effectiveBLen && effectiveALen == matched) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 return true;
576 }
Wei Huangfd7b4f12009-10-22 18:03:48 -0700577
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 return false;
579 }
580
581 // At least one string has matched completely;
Taesu Leec180e462019-05-28 19:05:06 +0900582 if (matched >= minMatch && (ia < 0 || ib < 0)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 return true;
584 }
585
586 /*
587 * Now, what remains must be one of the following for a
588 * match:
589 *
590 * - a '+' on one and a '00' or a '011' on the other
591 * - a '0' on one and a (+,00)<country code> on the other
592 * (for this, a '0' and a '00' prefix would have succeeded above)
593 */
594
595 if (matchIntlPrefix(a, ia + 1)
596 && matchIntlPrefix (b, ib +1)
597 ) {
598 return true;
599 }
600
601 if (matchTrunkPrefix(a, ia + 1)
602 && matchIntlPrefixAndCC(b, ib +1)
603 ) {
604 return true;
605 }
606
607 if (matchTrunkPrefix(b, ib + 1)
608 && matchIntlPrefixAndCC(a, ia +1)
609 ) {
610 return true;
611 }
612
613 return false;
614 }
615
616 /**
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700617 * @hide
618 */
Mathew Inwooda8382062018-08-16 17:01:12 +0100619 @UnsupportedAppUsage
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700620 public static boolean
621 compareStrictly(String a, String b) {
622 return compareStrictly(a, b, true);
623 }
624
625 /**
626 * @hide
627 */
Mathew Inwooda8382062018-08-16 17:01:12 +0100628 @UnsupportedAppUsage
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700629 public static boolean
630 compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) {
631 if (a == null || b == null) {
632 return a == b;
633 } else if (a.length() == 0 && b.length() == 0) {
634 return false;
635 }
636
637 int forwardIndexA = 0;
638 int forwardIndexB = 0;
639
640 CountryCallingCodeAndNewIndex cccA =
641 tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix);
642 CountryCallingCodeAndNewIndex cccB =
643 tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix);
644 boolean bothHasCountryCallingCode = false;
645 boolean okToIgnorePrefix = true;
646 boolean trunkPrefixIsOmittedA = false;
647 boolean trunkPrefixIsOmittedB = false;
648 if (cccA != null && cccB != null) {
649 if (cccA.countryCallingCode != cccB.countryCallingCode) {
650 // Different Country Calling Code. Must be different phone number.
651 return false;
652 }
653 // When both have ccc, do not ignore trunk prefix. Without this,
654 // "+81123123" becomes same as "+810123123" (+81 == Japan)
655 okToIgnorePrefix = false;
656 bothHasCountryCallingCode = true;
657 forwardIndexA = cccA.newIndex;
658 forwardIndexB = cccB.newIndex;
659 } else if (cccA == null && cccB == null) {
660 // When both do not have ccc, do not ignore trunk prefix. Without this,
661 // "123123" becomes same as "0123123"
662 okToIgnorePrefix = false;
663 } else {
664 if (cccA != null) {
665 forwardIndexA = cccA.newIndex;
666 } else {
667 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
668 if (tmp >= 0) {
669 forwardIndexA = tmp;
670 trunkPrefixIsOmittedA = true;
671 }
672 }
673 if (cccB != null) {
674 forwardIndexB = cccB.newIndex;
675 } else {
676 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
677 if (tmp >= 0) {
678 forwardIndexB = tmp;
679 trunkPrefixIsOmittedB = true;
680 }
681 }
682 }
683
684 int backwardIndexA = a.length() - 1;
685 int backwardIndexB = b.length() - 1;
686 while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) {
687 boolean skip_compare = false;
688 final char chA = a.charAt(backwardIndexA);
689 final char chB = b.charAt(backwardIndexB);
690 if (isSeparator(chA)) {
691 backwardIndexA--;
692 skip_compare = true;
693 }
694 if (isSeparator(chB)) {
695 backwardIndexB--;
696 skip_compare = true;
697 }
698
699 if (!skip_compare) {
700 if (chA != chB) {
701 return false;
702 }
703 backwardIndexA--;
704 backwardIndexB--;
705 }
706 }
707
708 if (okToIgnorePrefix) {
709 if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) ||
710 !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) {
711 if (acceptInvalidCCCPrefix) {
712 // Maybe the code handling the special case for Thailand makes the
713 // result garbled, so disable the code and try again.
714 // e.g. "16610001234" must equal to "6610001234", but with
715 // Thailand-case handling code, they become equal to each other.
716 //
717 // Note: we select simplicity rather than adding some complicated
718 // logic here for performance(like "checking whether remaining
719 // numbers are just 66 or not"), assuming inputs are small
720 // enough.
721 return compare(a, b, false);
722 } else {
723 return false;
724 }
725 }
726 if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) ||
727 !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) {
728 if (acceptInvalidCCCPrefix) {
729 return compare(a, b, false);
730 } else {
731 return false;
732 }
733 }
734 } else {
735 // In the US, 1-650-555-1234 must be equal to 650-555-1234,
Jake Hambybcd57322010-04-23 19:34:41 -0700736 // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan.
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -0700737 // This request exists just in US (with 1 trunk (NDD) prefix).
738 // In addition, "011 11 7005554141" must not equal to "+17005554141",
739 // while "011 1 7005554141" must equal to "+17005554141"
740 //
741 // In this comparison, we ignore the prefix '1' just once, when
742 // - at least either does not have CCC, or
743 // - the remaining non-separator number is 1
744 boolean maybeNamp = !bothHasCountryCallingCode;
745 while (backwardIndexA >= forwardIndexA) {
746 final char chA = a.charAt(backwardIndexA);
747 if (isDialable(chA)) {
748 if (maybeNamp && tryGetISODigit(chA) == 1) {
749 maybeNamp = false;
750 } else {
751 return false;
752 }
753 }
754 backwardIndexA--;
755 }
756 while (backwardIndexB >= forwardIndexB) {
757 final char chB = b.charAt(backwardIndexB);
758 if (isDialable(chB)) {
759 if (maybeNamp && tryGetISODigit(chB) == 1) {
760 maybeNamp = false;
761 } else {
762 return false;
763 }
764 }
765 backwardIndexB--;
766 }
767 }
768
769 return true;
770 }
771
772 /**
Taesu Leec180e462019-05-28 19:05:06 +0900773 * Returns the rightmost minimum matched characters in the network portion
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 * in *reversed* order
775 *
776 * This can be used to do a database lookup against the column
777 * that stores getStrippedReversed()
778 *
779 * Returns null if phoneNumber == null
780 */
781 public static String
782 toCallerIDMinMatch(String phoneNumber) {
Tammo Spalink9e534152009-10-19 14:10:47 +0800783 String np = extractNetworkPortionAlt(phoneNumber);
Taesu Leec180e462019-05-28 19:05:06 +0900784 return internalGetStrippedReversed(np, getMinMatch());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785 }
786
787 /**
788 * Returns the network portion reversed.
789 * This string is intended to go into an index column for a
790 * database lookup.
791 *
792 * Returns null if phoneNumber == null
793 */
794 public static String
795 getStrippedReversed(String phoneNumber) {
Tammo Spalink9e534152009-10-19 14:10:47 +0800796 String np = extractNetworkPortionAlt(phoneNumber);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797
798 if (np == null) return null;
799
800 return internalGetStrippedReversed(np, np.length());
801 }
802
803 /**
804 * Returns the last numDigits of the reversed phone number
805 * Returns null if np == null
806 */
807 private static String
808 internalGetStrippedReversed(String np, int numDigits) {
809 if (np == null) return null;
810
811 StringBuilder ret = new StringBuilder(numDigits);
812 int length = np.length();
813
814 for (int i = length - 1, s = length
815 ; i >= 0 && (s - i) <= numDigits ; i--
816 ) {
817 char c = np.charAt(i);
818
819 ret.append(c);
820 }
821
822 return ret.toString();
823 }
824
825 /**
826 * Basically: makes sure there's a + in front of a
827 * TOA_International number
828 *
829 * Returns null if s == null
830 */
831 public static String
832 stringFromStringAndTOA(String s, int TOA) {
833 if (s == null) return null;
834
835 if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') {
836 return "+" + s;
837 }
838
839 return s;
840 }
841
842 /**
843 * Returns the TOA for the given dial string
844 * Basically, returns TOA_International if there's a + prefix
845 */
846
847 public static int
848 toaFromString(String s) {
849 if (s != null && s.length() > 0 && s.charAt(0) == '+') {
850 return TOA_International;
851 }
852
853 return TOA_Unknown;
854 }
855
856 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 * 3GPP TS 24.008 10.5.4.7
858 * Called Party BCD Number
859 *
860 * See Also TS 51.011 10.5.1 "dialing number/ssc string"
861 * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
862 *
863 * @param bytes the data buffer
864 * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
865 * @param length is the number of bytes including TOA byte
866 * and must be at least 2
867 *
868 * @return partial string on invalid decode
869 *
Erika Avenberg0be6d7c2014-08-30 12:00:47 +0200870 * @deprecated use {@link #calledPartyBCDToString(byte[], int, int, int)} instead. Calling this
871 * method is equivalent to calling {@link #calledPartyBCDToString(byte[], int, int)} with
872 * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 */
Erika Avenberg0be6d7c2014-08-30 12:00:47 +0200874 @Deprecated
875 public static String calledPartyBCDToString(byte[] bytes, int offset, int length) {
876 return calledPartyBCDToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
877 }
878
879 /**
880 * 3GPP TS 24.008 10.5.4.7
881 * Called Party BCD Number
882 *
883 * See Also TS 51.011 10.5.1 "dialing number/ssc string"
884 * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
885 *
886 * @param bytes the data buffer
887 * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
888 * @param length is the number of bytes including TOA byte
889 * and must be at least 2
890 * @param bcdExtType used to determine the extended bcd coding
891 * @see #BCD_EXTENDED_TYPE_EF_ADN
892 * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
893 *
894 */
895 public static String calledPartyBCDToString(
Pengquan Meng0879c902018-02-01 13:39:44 -0800896 byte[] bytes, int offset, int length, @BcdExtendType int bcdExtType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800897 boolean prependPlus = false;
898 StringBuilder ret = new StringBuilder(1 + length * 2);
899
900 if (length < 2) {
901 return "";
902 }
903
The Android Open Source Projectb1110142010-05-13 14:50:16 -0700904 //Only TON field should be taken in consideration
Samuel Holmbergce2a97a2010-02-24 12:15:15 +0100905 if ((bytes[offset] & 0xf0) == (TOA_International & 0xf0)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 prependPlus = true;
907 }
908
909 internalCalledPartyBCDFragmentToString(
Erika Avenberg0be6d7c2014-08-30 12:00:47 +0200910 ret, bytes, offset + 1, length - 1, bcdExtType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911
912 if (prependPlus && ret.length() == 0) {
913 // If the only thing there is a prepended plus, return ""
914 return "";
915 }
916
917 if (prependPlus) {
918 // This is an "international number" and should have
919 // a plus prepended to the dialing number. But there
Jake Hamby145ff602010-04-15 15:23:12 -0700920 // can also be GSM MMI codes as defined in TS 22.030 6.5.2
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 // so we need to handle those also.
922 //
Jake Hamby145ff602010-04-15 15:23:12 -0700923 // http://web.telia.com/~u47904776/gsmkode.htm
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 // has a nice list of some of these GSM codes.
925 //
926 // Examples are:
927 // **21*+886988171479#
928 // **21*8311234567#
929 // *21#
930 // #21#
931 // *#21#
932 // *31#+11234567890
933 // #31#+18311234567
934 // #31#8311234567
935 // 18311234567
936 // +18311234567#
937 // +18311234567
938 // Odd ball cases that some phones handled
939 // where there is no dialing number so they
940 // append the "+"
941 // *21#+
942 // **21#+
943 String retString = ret.toString();
944 Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$");
945 Matcher m = p.matcher(retString);
946 if (m.matches()) {
947 if ("".equals(m.group(2))) {
948 // Started with two [#*] ends with #
949 // So no dialing number and we'll just
950 // append a +, this handles **21#+
951 ret = new StringBuilder();
952 ret.append(m.group(1));
953 ret.append(m.group(3));
954 ret.append(m.group(4));
955 ret.append(m.group(5));
956 ret.append("+");
957 } else {
958 // Starts with [#*] and ends with #
959 // Assume group 4 is a dialing number
960 // such as *21*+1234554#
961 ret = new StringBuilder();
962 ret.append(m.group(1));
963 ret.append(m.group(2));
964 ret.append(m.group(3));
965 ret.append("+");
966 ret.append(m.group(4));
967 ret.append(m.group(5));
968 }
969 } else {
970 p = Pattern.compile("(^[#*])(.*)([#*])(.*)");
971 m = p.matcher(retString);
972 if (m.matches()) {
973 // Starts with [#*] and only one other [#*]
974 // Assume the data after last [#*] is dialing
975 // number (i.e. group 4) such as *31#+11234567890.
976 // This also includes the odd ball *21#+
977 ret = new StringBuilder();
978 ret.append(m.group(1));
979 ret.append(m.group(2));
980 ret.append(m.group(3));
981 ret.append("+");
982 ret.append(m.group(4));
983 } else {
984 // Does NOT start with [#*] just prepend '+'
985 ret = new StringBuilder();
986 ret.append('+');
987 ret.append(retString);
988 }
989 }
990 }
991
992 return ret.toString();
993 }
994
Erika Avenberg0be6d7c2014-08-30 12:00:47 +0200995 private static void internalCalledPartyBCDFragmentToString(
Pengquan Meng0879c902018-02-01 13:39:44 -0800996 StringBuilder sb, byte [] bytes, int offset, int length,
997 @BcdExtendType int bcdExtType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800998 for (int i = offset ; i < length + offset ; i++) {
999 byte b;
1000 char c;
1001
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001002 c = bcdToChar((byte)(bytes[i] & 0xf), bcdExtType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003
1004 if (c == 0) {
1005 return;
1006 }
1007 sb.append(c);
1008
1009 // FIXME(mkf) TS 23.040 9.1.2.3 says
1010 // "if a mobile receives 1111 in a position prior to
Jake Hamby145ff602010-04-15 15:23:12 -07001011 // the last semi-octet then processing shall commence with
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 // the next semi-octet and the intervening
1013 // semi-octet shall be ignored"
Jake Hamby145ff602010-04-15 15:23:12 -07001014 // How does this jive with 24.008 10.5.4.7
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015
1016 b = (byte)((bytes[i] >> 4) & 0xf);
1017
1018 if (b == 0xf && i + 1 == length + offset) {
1019 //ignore final 0xf
1020 break;
1021 }
1022
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001023 c = bcdToChar(b, bcdExtType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 if (c == 0) {
1025 return;
1026 }
1027
1028 sb.append(c);
1029 }
1030
1031 }
1032
1033 /**
1034 * Like calledPartyBCDToString, but field does not start with a
1035 * TOA byte. For example: SIM ADN extension fields
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001036 *
1037 * @deprecated use {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} instead.
1038 * Calling this method is equivalent to calling
1039 * {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} with
1040 * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001041 */
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001042 @Deprecated
1043 public static String calledPartyBCDFragmentToString(byte[] bytes, int offset, int length) {
1044 return calledPartyBCDFragmentToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
1045 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001047 /**
1048 * Like calledPartyBCDToString, but field does not start with a
1049 * TOA byte. For example: SIM ADN extension fields
1050 */
1051 public static String calledPartyBCDFragmentToString(
Pengquan Meng0879c902018-02-01 13:39:44 -08001052 byte[] bytes, int offset, int length, @BcdExtendType int bcdExtType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053 StringBuilder ret = new StringBuilder(length * 2);
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001054 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length, bcdExtType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 return ret.toString();
1056 }
1057
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001058 /**
1059 * Returns the correspond character for given {@code b} based on {@code bcdExtType}, or 0 on
1060 * invalid code.
1061 */
Pengquan Meng0879c902018-02-01 13:39:44 -08001062 private static char bcdToChar(byte b, @BcdExtendType int bcdExtType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 if (b < 0xa) {
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001064 return (char) ('0' + b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 }
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001066
1067 String extended = null;
1068 if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
1069 extended = BCD_EF_ADN_EXTENDED;
1070 } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
1071 extended = BCD_CALLED_PARTY_EXTENDED;
1072 }
1073 if (extended == null || b - 0xa >= extended.length()) {
1074 return 0;
1075 }
1076
1077 return extended.charAt(b - 0xa);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 }
1079
Pengquan Meng0879c902018-02-01 13:39:44 -08001080 private static int charToBCD(char c, @BcdExtendType int bcdExtType) {
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001081 if ('0' <= c && c <= '9') {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 return c - '0';
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 }
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001084
1085 String extended = null;
1086 if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
1087 extended = BCD_EF_ADN_EXTENDED;
1088 } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
1089 extended = BCD_CALLED_PARTY_EXTENDED;
1090 }
1091 if (extended == null || extended.indexOf(c) == -1) {
1092 throw new RuntimeException("invalid char for BCD " + c);
1093 }
1094 return 0xa + extended.indexOf(c);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 }
1096
1097 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001098 * Return true iff the network portion of <code>address</code> is,
1099 * as far as we can tell on the device, suitable for use as an SMS
1100 * destination address.
1101 */
1102 public static boolean isWellFormedSmsAddress(String address) {
1103 String networkPortion =
1104 PhoneNumberUtils.extractNetworkPortion(address);
1105
1106 return (!(networkPortion.equals("+")
1107 || TextUtils.isEmpty(networkPortion)))
1108 && isDialable(networkPortion);
1109 }
1110
1111 public static boolean isGlobalPhoneNumber(String phoneNumber) {
1112 if (TextUtils.isEmpty(phoneNumber)) {
1113 return false;
1114 }
1115
1116 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber);
1117 return match.matches();
1118 }
1119
1120 private static boolean isDialable(String address) {
1121 for (int i = 0, count = address.length(); i < count; i++) {
1122 if (!isDialable(address.charAt(i))) {
1123 return false;
1124 }
1125 }
1126 return true;
1127 }
1128
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05001129 private static boolean isNonSeparator(String address) {
1130 for (int i = 0, count = address.length(); i < count; i++) {
1131 if (!isNonSeparator(address.charAt(i))) {
1132 return false;
1133 }
1134 }
1135 return true;
1136 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 /**
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001138 * Note: calls extractNetworkPortion(), so do not use for
1139 * SIM EF[ADN] style records
1140 *
1141 * Returns null if network portion is empty.
1142 */
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001143 public static byte[] networkPortionToCalledPartyBCD(String s) {
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001144 String networkPortion = extractNetworkPortion(s);
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001145 return numberToCalledPartyBCDHelper(
1146 networkPortion, false, BCD_EXTENDED_TYPE_EF_ADN);
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001147 }
1148
1149 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
1151 * one-byte length prefix.
1152 */
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001153 public static byte[] networkPortionToCalledPartyBCDWithLength(String s) {
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001154 String networkPortion = extractNetworkPortion(s);
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001155 return numberToCalledPartyBCDHelper(
1156 networkPortion, true, BCD_EXTENDED_TYPE_EF_ADN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 }
1158
1159 /**
1160 * Convert a dialing number to BCD byte array
1161 *
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001162 * @param number dialing number string. If the dialing number starts with '+', set to
1163 * international TOA
1164 *
1165 * @return BCD byte array
1166 *
1167 * @deprecated use {@link #numberToCalledPartyBCD(String, int)} instead. Calling this method
1168 * is equivalent to calling {@link #numberToCalledPartyBCD(String, int)} with
1169 * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
1170 */
1171 @Deprecated
1172 public static byte[] numberToCalledPartyBCD(String number) {
1173 return numberToCalledPartyBCD(number, BCD_EXTENDED_TYPE_EF_ADN);
1174 }
1175
1176 /**
1177 * Convert a dialing number to BCD byte array
1178 *
1179 * @param number dialing number string. If the dialing number starts with '+', set to
1180 * international TOA
1181 * @param bcdExtType used to determine the extended bcd coding
1182 * @see #BCD_EXTENDED_TYPE_EF_ADN
1183 * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
1184 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185 * @return BCD byte array
1186 */
Pengquan Meng0879c902018-02-01 13:39:44 -08001187 public static byte[] numberToCalledPartyBCD(String number, @BcdExtendType int bcdExtType) {
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001188 return numberToCalledPartyBCDHelper(number, false, bcdExtType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189 }
1190
1191 /**
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001192 * If includeLength is true, prepend a one-byte length value to
1193 * the return array.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 */
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001195 private static byte[] numberToCalledPartyBCDHelper(
Pengquan Meng0879c902018-02-01 13:39:44 -08001196 String number, boolean includeLength, @BcdExtendType int bcdExtType) {
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001197 int numberLenReal = number.length();
1198 int numberLenEffective = numberLenReal;
1199 boolean hasPlus = number.indexOf('+') != -1;
1200 if (hasPlus) numberLenEffective--;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001202 if (numberLenEffective == 0) return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001204 int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each.
1205 int extraBytes = 1; // Prepended TOA byte.
1206 if (includeLength) extraBytes++; // Optional prepended length byte.
1207 resultLen += extraBytes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001209 byte[] result = new byte[resultLen];
1210
1211 int digitCount = 0;
1212 for (int i = 0; i < numberLenReal; i++) {
1213 char c = number.charAt(i);
1214 if (c == '+') continue;
1215 int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
Erika Avenberg0be6d7c2014-08-30 12:00:47 +02001216 result[extraBytes + (digitCount >> 1)] |=
1217 (byte)((charToBCD(c, bcdExtType) & 0x0F) << shift);
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001218 digitCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219 }
1220
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001221 // 1-fill any trailing odd nibble/quartet.
1222 if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223
Tammo Spalink3a08cec2009-06-25 16:13:51 +08001224 int offset = 0;
1225 if (includeLength) result[offset++] = (byte)(resultLen - 1);
1226 result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown);
1227
1228 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229 }
1230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 //================ Number formatting =========================
1232
1233 /** The current locale is unknown, look for a country code or don't format */
1234 public static final int FORMAT_UNKNOWN = 0;
1235 /** NANP formatting */
1236 public static final int FORMAT_NANP = 1;
1237 /** Japanese formatting */
1238 public static final int FORMAT_JAPAN = 2;
1239
1240 /** List of country codes for countries that use the NANP */
1241 private static final String[] NANP_COUNTRIES = new String[] {
1242 "US", // United States
1243 "CA", // Canada
1244 "AS", // American Samoa
1245 "AI", // Anguilla
1246 "AG", // Antigua and Barbuda
1247 "BS", // Bahamas
1248 "BB", // Barbados
1249 "BM", // Bermuda
1250 "VG", // British Virgin Islands
1251 "KY", // Cayman Islands
1252 "DM", // Dominica
1253 "DO", // Dominican Republic
1254 "GD", // Grenada
1255 "GU", // Guam
1256 "JM", // Jamaica
1257 "PR", // Puerto Rico
1258 "MS", // Montserrat
Libin Tang7850cdd2009-08-18 13:22:47 -05001259 "MP", // Northern Mariana Islands
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 "KN", // Saint Kitts and Nevis
1261 "LC", // Saint Lucia
1262 "VC", // Saint Vincent and the Grenadines
1263 "TT", // Trinidad and Tobago
1264 "TC", // Turks and Caicos Islands
1265 "VI", // U.S. Virgin Islands
1266 };
1267
Roshan Pius672b2cc2015-08-25 11:23:49 -07001268 private static final String KOREA_ISO_COUNTRY_CODE = "KR";
1269
Tyler Gunn3b8f62b2017-02-12 21:26:13 -08001270 private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
1271
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001272 /**
1273 * Breaks the given number down and formats it according to the rules
1274 * for the country the number is from.
1275 *
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001276 * @param source The phone number to format
1277 * @return A locally acceptable formatting of the input, or the raw input if
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 * formatting rules aren't known for the number
Yorke Lee99d16992013-11-15 18:44:08 -08001279 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001280 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 */
Ben Gilad95cde2d2015-01-08 17:56:28 -08001282 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 public static String formatNumber(String source) {
1284 SpannableStringBuilder text = new SpannableStringBuilder(source);
1285 formatNumber(text, getFormatTypeForLocale(Locale.getDefault()));
1286 return text.toString();
1287 }
1288
1289 /**
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001290 * Formats the given number with the given formatting type. Currently
1291 * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type.
1292 *
1293 * @param source the phone number to format
1294 * @param defaultFormattingType The default formatting rules to apply if the number does
Jake Hamby145ff602010-04-15 15:23:12 -07001295 * not begin with +[country_code]
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001296 * @return The phone number formatted with the given formatting type.
1297 *
Yorke Lee99d16992013-11-15 18:44:08 -08001298 * @hide
Wink Savillefb40dd42014-06-12 17:02:31 -07001299 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001300 */
Ben Gilad95cde2d2015-01-08 17:56:28 -08001301 @Deprecated
Mathew Inwooda8382062018-08-16 17:01:12 +01001302 @UnsupportedAppUsage
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001303 public static String formatNumber(String source, int defaultFormattingType) {
1304 SpannableStringBuilder text = new SpannableStringBuilder(source);
1305 formatNumber(text, defaultFormattingType);
1306 return text.toString();
1307 }
1308
1309 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001310 * Returns the phone number formatting type for the given locale.
1311 *
1312 * @param locale The locale of interest, usually {@link Locale#getDefault()}
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001313 * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 * rules are not known for the given locale
Yorke Lee99d16992013-11-15 18:44:08 -08001315 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001316 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001317 */
Ben Gilad95cde2d2015-01-08 17:56:28 -08001318 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 public static int getFormatTypeForLocale(Locale locale) {
1320 String country = locale.getCountry();
1321
Libin Tang7850cdd2009-08-18 13:22:47 -05001322 return getFormatTypeFromCountryCode(country);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 }
1324
1325 /**
Daisuke Miyakawad95a02c2009-10-27 19:27:00 +09001326 * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP}
1327 * is supported as a second argument.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001328 *
1329 * @param text The number to be formatted, will be modified with the formatting
1330 * @param defaultFormattingType The default formatting rules to apply if the number does
Jake Hamby145ff602010-04-15 15:23:12 -07001331 * not begin with +[country_code]
Yorke Lee99d16992013-11-15 18:44:08 -08001332 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001333 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001334 */
Ben Gilad95cde2d2015-01-08 17:56:28 -08001335 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 public static void formatNumber(Editable text, int defaultFormattingType) {
1337 int formatType = defaultFormattingType;
1338
1339 if (text.length() > 2 && text.charAt(0) == '+') {
1340 if (text.charAt(1) == '1') {
1341 formatType = FORMAT_NANP;
1342 } else if (text.length() >= 3 && text.charAt(1) == '8'
1343 && text.charAt(2) == '1') {
1344 formatType = FORMAT_JAPAN;
1345 } else {
Lars Dunemark47633f82010-02-08 14:04:38 +01001346 formatType = FORMAT_UNKNOWN;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347 }
1348 }
1349
1350 switch (formatType) {
1351 case FORMAT_NANP:
1352 formatNanpNumber(text);
1353 return;
1354 case FORMAT_JAPAN:
1355 formatJapaneseNumber(text);
1356 return;
Lars Dunemark47633f82010-02-08 14:04:38 +01001357 case FORMAT_UNKNOWN:
1358 removeDashes(text);
1359 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001360 }
1361 }
1362
1363 private static final int NANP_STATE_DIGIT = 1;
1364 private static final int NANP_STATE_PLUS = 2;
1365 private static final int NANP_STATE_ONE = 3;
1366 private static final int NANP_STATE_DASH = 4;
1367
1368 /**
1369 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted
1370 * as:
1371 *
1372 * <p><code>
Ficus Kirkpatrick52143762009-05-26 18:28:38 -07001373 * xxxxx
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001374 * xxx-xxxx
1375 * xxx-xxx-xxxx
1376 * 1-xxx-xxx-xxxx
1377 * +1-xxx-xxx-xxxx
1378 * </code></p>
1379 *
1380 * @param text the number to be formatted, will be modified with the formatting
Yorke Lee99d16992013-11-15 18:44:08 -08001381 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001382 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001383 */
Ben Gilad95cde2d2015-01-08 17:56:28 -08001384 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001385 public static void formatNanpNumber(Editable text) {
1386 int length = text.length();
1387 if (length > "+1-nnn-nnn-nnnn".length()) {
1388 // The string is too long to be formatted
1389 return;
Ficus Kirkpatrick52143762009-05-26 18:28:38 -07001390 } else if (length <= 5) {
1391 // The string is either a shortcode or too short to be formatted
1392 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393 }
Ficus Kirkpatrick52143762009-05-26 18:28:38 -07001394
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395 CharSequence saved = text.subSequence(0, length);
1396
1397 // Strip the dashes first, as we're going to add them back
Lars Dunemark47633f82010-02-08 14:04:38 +01001398 removeDashes(text);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399 length = text.length();
1400
1401 // When scanning the number we record where dashes need to be added,
1402 // if they're non-0 at the end of the scan the dashes will be added in
1403 // the proper places.
1404 int dashPositions[] = new int[3];
1405 int numDashes = 0;
1406
1407 int state = NANP_STATE_DIGIT;
1408 int numDigits = 0;
1409 for (int i = 0; i < length; i++) {
1410 char c = text.charAt(i);
1411 switch (c) {
1412 case '1':
1413 if (numDigits == 0 || state == NANP_STATE_PLUS) {
1414 state = NANP_STATE_ONE;
1415 break;
1416 }
1417 // fall through
1418 case '2':
1419 case '3':
1420 case '4':
1421 case '5':
1422 case '6':
1423 case '7':
1424 case '8':
1425 case '9':
1426 case '0':
1427 if (state == NANP_STATE_PLUS) {
1428 // Only NANP number supported for now
1429 text.replace(0, length, saved);
1430 return;
1431 } else if (state == NANP_STATE_ONE) {
1432 // Found either +1 or 1, follow it up with a dash
1433 dashPositions[numDashes++] = i;
1434 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) {
1435 // Found a digit that should be after a dash that isn't
1436 dashPositions[numDashes++] = i;
1437 }
1438 state = NANP_STATE_DIGIT;
1439 numDigits++;
1440 break;
1441
1442 case '-':
1443 state = NANP_STATE_DASH;
1444 break;
1445
1446 case '+':
1447 if (i == 0) {
1448 // Plus is only allowed as the first character
1449 state = NANP_STATE_PLUS;
1450 break;
1451 }
1452 // Fall through
1453 default:
1454 // Unknown character, bail on formatting
1455 text.replace(0, length, saved);
1456 return;
1457 }
1458 }
1459
1460 if (numDigits == 7) {
1461 // With 7 digits we want xxx-xxxx, not xxx-xxx-x
1462 numDashes--;
1463 }
1464
1465 // Actually put the dashes in place
1466 for (int i = 0; i < numDashes; i++) {
1467 int pos = dashPositions[i];
1468 text.replace(pos + i, pos + i, "-");
1469 }
1470
1471 // Remove trailing dashes
1472 int len = text.length();
1473 while (len > 0) {
1474 if (text.charAt(len - 1) == '-') {
1475 text.delete(len - 1, len);
1476 len--;
1477 } else {
1478 break;
1479 }
1480 }
1481 }
1482
1483 /**
1484 * Formats a phone number in-place using the Japanese formatting rules.
1485 * Numbers will be formatted as:
1486 *
1487 * <p><code>
1488 * 03-xxxx-xxxx
1489 * 090-xxxx-xxxx
1490 * 0120-xxx-xxx
1491 * +81-3-xxxx-xxxx
1492 * +81-90-xxxx-xxxx
1493 * </code></p>
1494 *
1495 * @param text the number to be formatted, will be modified with
1496 * the formatting
Yorke Lee99d16992013-11-15 18:44:08 -08001497 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001498 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001499 */
Ben Gilad95cde2d2015-01-08 17:56:28 -08001500 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 public static void formatJapaneseNumber(Editable text) {
1502 JapanesePhoneNumberFormatter.format(text);
1503 }
1504
Lars Dunemark47633f82010-02-08 14:04:38 +01001505 /**
1506 * Removes all dashes from the number.
1507 *
1508 * @param text the number to clear from dashes
1509 */
1510 private static void removeDashes(Editable text) {
1511 int p = 0;
1512 while (p < text.length()) {
1513 if (text.charAt(p) == '-') {
1514 text.delete(p, p + 1);
1515 } else {
1516 p++;
1517 }
1518 }
1519 }
1520
Bai Taofae6ec92010-07-29 05:36:54 +08001521 /**
Ben Gilad95cde2d2015-01-08 17:56:28 -08001522 * Formats the specified {@code phoneNumber} to the E.164 representation.
Bai Taofae6ec92010-07-29 05:36:54 +08001523 *
Ben Gilad95cde2d2015-01-08 17:56:28 -08001524 * @param phoneNumber the phone number to format.
1525 * @param defaultCountryIso the ISO 3166-1 two letters country code.
1526 * @return the E.164 representation, or null if the given phone number is not valid.
Bai Taofae6ec92010-07-29 05:36:54 +08001527 */
1528 public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) {
Ben Gilad95cde2d2015-01-08 17:56:28 -08001529 return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.E164);
1530 }
1531
1532 /**
1533 * Formats the specified {@code phoneNumber} to the RFC3966 representation.
1534 *
1535 * @param phoneNumber the phone number to format.
1536 * @param defaultCountryIso the ISO 3166-1 two letters country code.
1537 * @return the RFC3966 representation, or null if the given phone number is not valid.
1538 */
1539 public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) {
1540 return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.RFC3966);
1541 }
1542
1543 /**
1544 * Formats the raw phone number (string) using the specified {@code formatIdentifier}.
1545 * <p>
1546 * The given phone number must have an area code and could have a country code.
1547 * <p>
1548 * The defaultCountryIso is used to validate the given number and generate the formatted number
1549 * if the specified number doesn't have a country code.
1550 *
1551 * @param rawPhoneNumber The phone number to format.
1552 * @param defaultCountryIso The ISO 3166-1 two letters country code.
1553 * @param formatIdentifier The (enum) identifier of the desired format.
1554 * @return the formatted representation, or null if the specified number is not valid.
1555 */
1556 private static String formatNumberInternal(
1557 String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier) {
1558
Bai Taofae6ec92010-07-29 05:36:54 +08001559 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
Bai Taofae6ec92010-07-29 05:36:54 +08001560 try {
Ben Gilad95cde2d2015-01-08 17:56:28 -08001561 PhoneNumber phoneNumber = util.parse(rawPhoneNumber, defaultCountryIso);
1562 if (util.isValidNumber(phoneNumber)) {
1563 return util.format(phoneNumber, formatIdentifier);
Bai Taofae6ec92010-07-29 05:36:54 +08001564 }
Ben Gilad95cde2d2015-01-08 17:56:28 -08001565 } catch (NumberParseException ignored) { }
1566
1567 return null;
Bai Taofae6ec92010-07-29 05:36:54 +08001568 }
1569
1570 /**
Tyler Gunnef5a4012017-02-16 16:21:14 -08001571 * Determines if a {@param phoneNumber} is international if dialed from
1572 * {@param defaultCountryIso}.
1573 *
1574 * @param phoneNumber The phone number.
1575 * @param defaultCountryIso The current country ISO.
1576 * @return {@code true} if the number is international, {@code false} otherwise.
1577 * @hide
1578 */
1579 public static boolean isInternationalNumber(String phoneNumber, String defaultCountryIso) {
Tyler Gunn438559e2017-05-31 14:35:39 -07001580 // If no phone number is provided, it can't be international.
1581 if (TextUtils.isEmpty(phoneNumber)) {
1582 return false;
1583 }
1584
Tyler Gunnef5a4012017-02-16 16:21:14 -08001585 // If it starts with # or * its not international.
1586 if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
1587 return false;
1588 }
1589
1590 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1591 try {
1592 PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
1593 return pn.getCountryCode() != util.getCountryCodeForRegion(defaultCountryIso);
1594 } catch (NumberParseException e) {
1595 return false;
1596 }
1597 }
1598
1599 /**
Bai Taofae6ec92010-07-29 05:36:54 +08001600 * Format a phone number.
1601 * <p>
1602 * If the given number doesn't have the country code, the phone will be
1603 * formatted to the default country's convention.
1604 *
1605 * @param phoneNumber
1606 * the number to be formatted.
1607 * @param defaultCountryIso
1608 * the ISO 3166-1 two letters country code whose convention will
1609 * be used if the given number doesn't have the country code.
1610 * @return the formatted number, or null if the given number is not valid.
Bai Taofae6ec92010-07-29 05:36:54 +08001611 */
1612 public static String formatNumber(String phoneNumber, String defaultCountryIso) {
Flavio Lerda2613e002011-09-26 18:40:37 +01001613 // Do not attempt to format numbers that start with a hash or star symbol.
1614 if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
1615 return phoneNumber;
1616 }
1617
Bai Taofae6ec92010-07-29 05:36:54 +08001618 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1619 String result = null;
1620 try {
Bai Tao2a4e9602010-08-25 14:10:10 +08001621 PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
Tyler Gunn3b8f62b2017-02-12 21:26:13 -08001622
1623 if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
Roshan Piusa9acb4c2015-08-31 13:04:14 -07001624 (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) &&
1625 (pn.getCountryCodeSource() ==
1626 PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) {
Tyler Gunn3b8f62b2017-02-12 21:26:13 -08001627 /**
1628 * Need to reformat any local Korean phone numbers (when the user is in Korea) with
1629 * country code to corresponding national format which would replace the leading
1630 * +82 with 0.
1631 */
1632 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
1633 } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
1634 pn.getCountryCode() == util.getCountryCodeForRegion(JAPAN_ISO_COUNTRY_CODE) &&
1635 (pn.getCountryCodeSource() ==
1636 PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) {
1637 /**
1638 * Need to reformat Japanese phone numbers (when user is in Japan) with the national
1639 * dialing format.
1640 */
Roshan Pius672b2cc2015-08-25 11:23:49 -07001641 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
1642 } else {
1643 result = util.formatInOriginalFormat(pn, defaultCountryIso);
1644 }
Bai Taofae6ec92010-07-29 05:36:54 +08001645 } catch (NumberParseException e) {
1646 }
1647 return result;
1648 }
1649
1650 /**
Bai Tao2a4e9602010-08-25 14:10:10 +08001651 * Format the phone number only if the given number hasn't been formatted.
1652 * <p>
1653 * The number which has only dailable character is treated as not being
1654 * formatted.
1655 *
1656 * @param phoneNumber
1657 * the number to be formatted.
1658 * @param phoneNumberE164
1659 * the E164 format number whose country code is used if the given
1660 * phoneNumber doesn't have the country code.
1661 * @param defaultCountryIso
1662 * the ISO 3166-1 two letters country code whose convention will
Shaopeng Jia26cd2432012-02-02 16:52:26 +01001663 * be used if the phoneNumberE164 is null or invalid, or if phoneNumber
1664 * contains IDD.
Bai Tao2a4e9602010-08-25 14:10:10 +08001665 * @return the formatted number if the given number has been formatted,
1666 * otherwise, return the given number.
Bai Tao2a4e9602010-08-25 14:10:10 +08001667 */
1668 public static String formatNumber(
1669 String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
1670 int len = phoneNumber.length();
1671 for (int i = 0; i < len; i++) {
1672 if (!isDialable(phoneNumber.charAt(i))) {
1673 return phoneNumber;
1674 }
1675 }
1676 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1677 // Get the country code from phoneNumberE164
1678 if (phoneNumberE164 != null && phoneNumberE164.length() >= 2
1679 && phoneNumberE164.charAt(0) == '+') {
1680 try {
Shaopeng Jia26cd2432012-02-02 16:52:26 +01001681 // The number to be parsed is in E164 format, so the default region used doesn't
1682 // matter.
1683 PhoneNumber pn = util.parse(phoneNumberE164, "ZZ");
Bai Tao2a4e9602010-08-25 14:10:10 +08001684 String regionCode = util.getRegionCodeForNumber(pn);
Shaopeng Jia26cd2432012-02-02 16:52:26 +01001685 if (!TextUtils.isEmpty(regionCode) &&
1686 // This makes sure phoneNumber doesn't contain an IDD
1687 normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) {
Bai Tao2a4e9602010-08-25 14:10:10 +08001688 defaultCountryIso = regionCode;
1689 }
1690 } catch (NumberParseException e) {
1691 }
1692 }
1693 String result = formatNumber(phoneNumber, defaultCountryIso);
1694 return result != null ? result : phoneNumber;
1695 }
1696
1697 /**
Bai Taofae6ec92010-07-29 05:36:54 +08001698 * Normalize a phone number by removing the characters other than digits. If
1699 * the given number has keypad letters, the letters will be converted to
1700 * digits first.
Dmitri Plotnikov3e4b7c32010-08-13 11:09:29 -07001701 *
Yorke Lee99d16992013-11-15 18:44:08 -08001702 * @param phoneNumber the number to be normalized.
Bai Taofae6ec92010-07-29 05:36:54 +08001703 * @return the normalized number.
Bai Taofae6ec92010-07-29 05:36:54 +08001704 */
1705 public static String normalizeNumber(String phoneNumber) {
Andrew Leeb31c3102014-05-14 12:13:02 -07001706 if (TextUtils.isEmpty(phoneNumber)) {
1707 return "";
1708 }
1709
Bai Taofae6ec92010-07-29 05:36:54 +08001710 StringBuilder sb = new StringBuilder();
1711 int len = phoneNumber.length();
1712 for (int i = 0; i < len; i++) {
1713 char c = phoneNumber.charAt(i);
Jake Hambye333c822012-01-13 13:14:39 -08001714 // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
1715 int digit = Character.digit(c, 10);
1716 if (digit != -1) {
1717 sb.append(digit);
Raph Levien50e92282014-12-15 15:46:17 -08001718 } else if (sb.length() == 0 && c == '+') {
Bai Taofae6ec92010-07-29 05:36:54 +08001719 sb.append(c);
Dmitri Plotnikov3e4b7c32010-08-13 11:09:29 -07001720 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
Bai Taofae6ec92010-07-29 05:36:54 +08001721 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
1722 }
1723 }
1724 return sb.toString();
1725 }
1726
jshinf80310d2012-01-13 10:25:16 -08001727 /**
Yorke Lee99d16992013-11-15 18:44:08 -08001728 * Replaces all unicode(e.g. Arabic, Persian) digits with their decimal digit equivalents.
jshinf80310d2012-01-13 10:25:16 -08001729 *
Yorke Lee99d16992013-11-15 18:44:08 -08001730 * @param number the number to perform the replacement on.
1731 * @return the replaced number.
jshinf80310d2012-01-13 10:25:16 -08001732 */
1733 public static String replaceUnicodeDigits(String number) {
1734 StringBuilder normalizedDigits = new StringBuilder(number.length());
1735 for (char c : number.toCharArray()) {
1736 int digit = Character.digit(c, 10);
1737 if (digit != -1) {
1738 normalizedDigits.append(digit);
1739 } else {
1740 normalizedDigits.append(c);
1741 }
1742 }
1743 return normalizedDigits.toString();
1744 }
1745
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001746 /**
David Brown1a811692011-11-08 11:11:56 -08001747 * Checks a given number against the list of
1748 * emergency numbers provided by the RIL and SIM card.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001749 *
1750 * @param number the number to look up.
David Brown1a811692011-11-08 11:11:56 -08001751 * @return true if the number is in the list of emergency numbers
1752 * listed in the RIL / SIM, otherwise return false.
sqian46c0c302018-12-27 14:12:11 -08001753 *
sqian3b5f87f2019-02-22 15:54:47 -08001754 * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)} instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001755 */
sqian46c0c302018-12-27 14:12:11 -08001756 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001757 public static boolean isEmergencyNumber(String number) {
Wink Savillefb40dd42014-06-12 17:02:31 -07001758 return isEmergencyNumber(getDefaultVoiceSubId(), number);
1759 }
1760
1761 /**
1762 * Checks a given number against the list of
1763 * emergency numbers provided by the RIL and SIM card.
1764 *
1765 * @param subId the subscription id of the SIM.
1766 * @param number the number to look up.
1767 * @return true if the number is in the list of emergency numbers
1768 * listed in the RIL / SIM, otherwise return false.
sqian46c0c302018-12-27 14:12:11 -08001769 *
sqian3b5f87f2019-02-22 15:54:47 -08001770 * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001771 * instead.
1772 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001773 * @hide
1774 */
sqian46c0c302018-12-27 14:12:11 -08001775 @Deprecated
Mathew Inwooda8382062018-08-16 17:01:12 +01001776 @UnsupportedAppUsage
Wink Saville63f03dd2014-10-23 10:44:45 -07001777 public static boolean isEmergencyNumber(int subId, String number) {
David Brown1a811692011-11-08 11:11:56 -08001778 // Return true only if the specified number *exactly* matches
1779 // one of the emergency numbers listed by the RIL / SIM.
Wink Savillefb40dd42014-06-12 17:02:31 -07001780 return isEmergencyNumberInternal(subId, number, true /* useExactMatch */);
David Brown1a811692011-11-08 11:11:56 -08001781 }
1782
1783 /**
1784 * Checks if given number might *potentially* result in
1785 * a call to an emergency service on the current network.
1786 *
1787 * Specifically, this method will return true if the specified number
1788 * is an emergency number according to the list managed by the RIL or
1789 * SIM, *or* if the specified number simply starts with the same
1790 * digits as any of the emergency numbers listed in the RIL / SIM.
1791 *
1792 * This method is intended for internal use by the phone app when
1793 * deciding whether to allow ACTION_CALL intents from 3rd party apps
1794 * (where we're required to *not* allow emergency calls to be placed.)
1795 *
1796 * @param number the number to look up.
1797 * @return true if the number is in the list of emergency numbers
1798 * listed in the RIL / SIM, *or* if the number starts with the
1799 * same digits as any of those emergency numbers.
1800 *
sqian3b5f87f2019-02-22 15:54:47 -08001801 * @deprecated Please use {@link TelephonyManager#isPotentialEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001802 * instead.
1803 *
David Brown1a811692011-11-08 11:11:56 -08001804 * @hide
1805 */
sqian46c0c302018-12-27 14:12:11 -08001806 @Deprecated
David Brown1a811692011-11-08 11:11:56 -08001807 public static boolean isPotentialEmergencyNumber(String number) {
Wink Savillefb40dd42014-06-12 17:02:31 -07001808 return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number);
1809 }
1810
1811 /**
1812 * Checks if given number might *potentially* result in
1813 * a call to an emergency service on the current network.
1814 *
1815 * Specifically, this method will return true if the specified number
1816 * is an emergency number according to the list managed by the RIL or
1817 * SIM, *or* if the specified number simply starts with the same
1818 * digits as any of the emergency numbers listed in the RIL / SIM.
1819 *
1820 * This method is intended for internal use by the phone app when
1821 * deciding whether to allow ACTION_CALL intents from 3rd party apps
1822 * (where we're required to *not* allow emergency calls to be placed.)
1823 *
1824 * @param subId the subscription id of the SIM.
1825 * @param number the number to look up.
1826 * @return true if the number is in the list of emergency numbers
1827 * listed in the RIL / SIM, *or* if the number starts with the
1828 * same digits as any of those emergency numbers.
sqian46c0c302018-12-27 14:12:11 -08001829 *
sqian3b5f87f2019-02-22 15:54:47 -08001830 * @deprecated Please use {@link TelephonyManager#isPotentialEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001831 * instead.
1832 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001833 * @hide
1834 */
Mathew Inwooda8382062018-08-16 17:01:12 +01001835 @UnsupportedAppUsage
sqian46c0c302018-12-27 14:12:11 -08001836 @Deprecated
Wink Saville63f03dd2014-10-23 10:44:45 -07001837 public static boolean isPotentialEmergencyNumber(int subId, String number) {
David Brown1a811692011-11-08 11:11:56 -08001838 // Check against the emergency numbers listed by the RIL / SIM,
1839 // and *don't* require an exact match.
Wink Savillefb40dd42014-06-12 17:02:31 -07001840 return isEmergencyNumberInternal(subId, number, false /* useExactMatch */);
David Brown1a811692011-11-08 11:11:56 -08001841 }
1842
1843 /**
1844 * Helper function for isEmergencyNumber(String) and
1845 * isPotentialEmergencyNumber(String).
1846 *
1847 * @param number the number to look up.
1848 *
1849 * @param useExactMatch if true, consider a number to be an emergency
1850 * number only if it *exactly* matches a number listed in
1851 * the RIL / SIM. If false, a number is considered to be an
1852 * emergency number if it simply starts with the same digits
1853 * as any of the emergency numbers listed in the RIL / SIM.
1854 * (Setting useExactMatch to false allows you to identify
1855 * number that could *potentially* result in emergency calls
1856 * since many networks will actually ignore trailing digits
1857 * after a valid emergency number.)
1858 *
1859 * @return true if the number is in the list of emergency numbers
1860 * listed in the RIL / sim, otherwise return false.
1861 */
1862 private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) {
Wink Savillefb40dd42014-06-12 17:02:31 -07001863 return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, useExactMatch);
1864 }
1865
1866 /**
1867 * Helper function for isEmergencyNumber(String) and
1868 * isPotentialEmergencyNumber(String).
1869 *
1870 * @param subId the subscription id of the SIM.
1871 * @param number the number to look up.
1872 *
1873 * @param useExactMatch if true, consider a number to be an emergency
1874 * number only if it *exactly* matches a number listed in
1875 * the RIL / SIM. If false, a number is considered to be an
1876 * emergency number if it simply starts with the same digits
1877 * as any of the emergency numbers listed in the RIL / SIM.
1878 * (Setting useExactMatch to false allows you to identify
1879 * number that could *potentially* result in emergency calls
1880 * since many networks will actually ignore trailing digits
1881 * after a valid emergency number.)
1882 *
1883 * @return true if the number is in the list of emergency numbers
1884 * listed in the RIL / sim, otherwise return false.
1885 */
Wink Saville63f03dd2014-10-23 10:44:45 -07001886 private static boolean isEmergencyNumberInternal(int subId, String number,
Wink Savillefb40dd42014-06-12 17:02:31 -07001887 boolean useExactMatch) {
1888 return isEmergencyNumberInternal(subId, number, null, useExactMatch);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001889 }
1890
1891 /**
Shaopeng Jia9683f992011-09-07 14:07:15 +02001892 * Checks if a given number is an emergency number for a specific country.
1893 *
1894 * @param number the number to look up.
1895 * @param defaultCountryIso the specific country which the number should be checked against
1896 * @return if the number is an emergency number for the specific country, then return true,
1897 * otherwise false
David Brown1a811692011-11-08 11:11:56 -08001898 *
sqian3b5f87f2019-02-22 15:54:47 -08001899 * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001900 * instead.
1901 *
Shaopeng Jia9683f992011-09-07 14:07:15 +02001902 * @hide
1903 */
sqian46c0c302018-12-27 14:12:11 -08001904 @Deprecated
Mathew Inwooda8382062018-08-16 17:01:12 +01001905 @UnsupportedAppUsage
Shaopeng Jia9683f992011-09-07 14:07:15 +02001906 public static boolean isEmergencyNumber(String number, String defaultCountryIso) {
Wink Savillefb40dd42014-06-12 17:02:31 -07001907 return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso);
1908 }
1909
1910 /**
1911 * Checks if a given number is an emergency number for a specific country.
1912 *
1913 * @param subId the subscription id of the SIM.
1914 * @param number the number to look up.
1915 * @param defaultCountryIso the specific country which the number should be checked against
1916 * @return if the number is an emergency number for the specific country, then return true,
1917 * otherwise false
sqian46c0c302018-12-27 14:12:11 -08001918 *
sqian3b5f87f2019-02-22 15:54:47 -08001919 * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001920 * instead.
1921 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001922 * @hide
1923 */
sqian46c0c302018-12-27 14:12:11 -08001924 @Deprecated
Wink Saville63f03dd2014-10-23 10:44:45 -07001925 public static boolean isEmergencyNumber(int subId, String number, String defaultCountryIso) {
Wink Savillefb40dd42014-06-12 17:02:31 -07001926 return isEmergencyNumberInternal(subId, number,
David Brown1a811692011-11-08 11:11:56 -08001927 defaultCountryIso,
1928 true /* useExactMatch */);
1929 }
1930
1931 /**
1932 * Checks if a given number might *potentially* result in a call to an
1933 * emergency service, for a specific country.
1934 *
1935 * Specifically, this method will return true if the specified number
1936 * is an emergency number in the specified country, *or* if the number
1937 * simply starts with the same digits as any emergency number for that
1938 * country.
1939 *
1940 * This method is intended for internal use by the phone app when
1941 * deciding whether to allow ACTION_CALL intents from 3rd party apps
1942 * (where we're required to *not* allow emergency calls to be placed.)
1943 *
1944 * @param number the number to look up.
1945 * @param defaultCountryIso the specific country which the number should be checked against
1946 * @return true if the number is an emergency number for the specific
1947 * country, *or* if the number starts with the same digits as
1948 * any of those emergency numbers.
1949 *
sqian3b5f87f2019-02-22 15:54:47 -08001950 * @deprecated Please use {@link TelephonyManager#isPotentialEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001951 * instead.
1952 *
David Brown1a811692011-11-08 11:11:56 -08001953 * @hide
1954 */
sqian46c0c302018-12-27 14:12:11 -08001955 @Deprecated
David Brown1a811692011-11-08 11:11:56 -08001956 public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) {
Wink Savillefb40dd42014-06-12 17:02:31 -07001957 return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso);
1958 }
1959
1960 /**
1961 * Checks if a given number might *potentially* result in a call to an
1962 * emergency service, for a specific country.
1963 *
1964 * Specifically, this method will return true if the specified number
1965 * is an emergency number in the specified country, *or* if the number
1966 * simply starts with the same digits as any emergency number for that
1967 * country.
1968 *
1969 * This method is intended for internal use by the phone app when
1970 * deciding whether to allow ACTION_CALL intents from 3rd party apps
1971 * (where we're required to *not* allow emergency calls to be placed.)
1972 *
1973 * @param subId the subscription id of the SIM.
1974 * @param number the number to look up.
1975 * @param defaultCountryIso the specific country which the number should be checked against
1976 * @return true if the number is an emergency number for the specific
1977 * country, *or* if the number starts with the same digits as
1978 * any of those emergency numbers.
sqian46c0c302018-12-27 14:12:11 -08001979 *
sqian3b5f87f2019-02-22 15:54:47 -08001980 * @deprecated Please use {@link TelephonyManager#isPotentialEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08001981 * instead.
1982 *
Wink Savillefb40dd42014-06-12 17:02:31 -07001983 * @hide
1984 */
sqian46c0c302018-12-27 14:12:11 -08001985 @Deprecated
Wink Saville63f03dd2014-10-23 10:44:45 -07001986 public static boolean isPotentialEmergencyNumber(int subId, String number,
Wink Savillefb40dd42014-06-12 17:02:31 -07001987 String defaultCountryIso) {
1988 return isEmergencyNumberInternal(subId, number,
David Brown1a811692011-11-08 11:11:56 -08001989 defaultCountryIso,
1990 false /* useExactMatch */);
1991 }
1992
1993 /**
1994 * Helper function for isEmergencyNumber(String, String) and
1995 * isPotentialEmergencyNumber(String, String).
1996 *
1997 * @param number the number to look up.
1998 * @param defaultCountryIso the specific country which the number should be checked against
1999 * @param useExactMatch if true, consider a number to be an emergency
2000 * number only if it *exactly* matches a number listed in
2001 * the RIL / SIM. If false, a number is considered to be an
2002 * emergency number if it simply starts with the same digits
2003 * as any of the emergency numbers listed in the RIL / SIM.
2004 *
2005 * @return true if the number is an emergency number for the specified country.
2006 */
2007 private static boolean isEmergencyNumberInternal(String number,
2008 String defaultCountryIso,
2009 boolean useExactMatch) {
Wink Savillefb40dd42014-06-12 17:02:31 -07002010 return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, defaultCountryIso,
2011 useExactMatch);
2012 }
2013
2014 /**
2015 * Helper function for isEmergencyNumber(String, String) and
2016 * isPotentialEmergencyNumber(String, String).
2017 *
2018 * @param subId the subscription id of the SIM.
2019 * @param number the number to look up.
2020 * @param defaultCountryIso the specific country which the number should be checked against
2021 * @param useExactMatch if true, consider a number to be an emergency
2022 * number only if it *exactly* matches a number listed in
2023 * the RIL / SIM. If false, a number is considered to be an
2024 * emergency number if it simply starts with the same digits
2025 * as any of the emergency numbers listed in the RIL / SIM.
2026 *
2027 * @return true if the number is an emergency number for the specified country.
2028 * @hide
2029 */
Wink Saville63f03dd2014-10-23 10:44:45 -07002030 private static boolean isEmergencyNumberInternal(int subId, String number,
Wink Savillefb40dd42014-06-12 17:02:31 -07002031 String defaultCountryIso,
2032 boolean useExactMatch) {
Hall Liub49a3af2020-01-21 15:34:02 -08002033 // TODO: clean up all the callers that pass in a defaultCountryIso, since it's ignored now.
sqian93711932019-04-01 19:26:18 -07002034 try {
2035 if (useExactMatch) {
2036 return TelephonyManager.getDefault().isEmergencyNumber(number);
2037 } else {
2038 return TelephonyManager.getDefault().isPotentialEmergencyNumber(number);
2039 }
2040 } catch (RuntimeException ex) {
2041 Rlog.e(LOG_TAG, "isEmergencyNumberInternal: RuntimeException: " + ex);
2042 }
2043 return false;
Shaopeng Jia9683f992011-09-07 14:07:15 +02002044 }
2045
2046 /**
Yorke Lee99d16992013-11-15 18:44:08 -08002047 * Checks if a given number is an emergency number for the country that the user is in.
Yorke Lee282129f2014-06-05 08:41:47 -07002048 *
Wink Savillefb40dd42014-06-12 17:02:31 -07002049 * @param number the number to look up.
2050 * @param context the specific context which the number should be checked against
Yorke Lee99d16992013-11-15 18:44:08 -08002051 * @return true if the specified number is an emergency number for the country the user
2052 * is currently in.
sqian46c0c302018-12-27 14:12:11 -08002053 *
sqian3b5f87f2019-02-22 15:54:47 -08002054 * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08002055 * instead.
Shaopeng Jia6b7c3f82011-09-14 17:36:18 +02002056 */
sqian46c0c302018-12-27 14:12:11 -08002057 @Deprecated
Yorke Lee282129f2014-06-05 08:41:47 -07002058 public static boolean isLocalEmergencyNumber(Context context, String number) {
Wink Savillefb40dd42014-06-12 17:02:31 -07002059 return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number);
2060 }
2061
2062 /**
2063 * Checks if a given number is an emergency number for the country that the user is in.
2064 *
2065 * @param subId the subscription id of the SIM.
2066 * @param number the number to look up.
2067 * @param context the specific context which the number should be checked against
2068 * @return true if the specified number is an emergency number for the country the user
2069 * is currently in.
sqian46c0c302018-12-27 14:12:11 -08002070 *
sqian3b5f87f2019-02-22 15:54:47 -08002071 * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08002072 * instead.
2073 *
Wink Savillefb40dd42014-06-12 17:02:31 -07002074 * @hide
2075 */
sqian46c0c302018-12-27 14:12:11 -08002076 @Deprecated
Mathew Inwooda8382062018-08-16 17:01:12 +01002077 @UnsupportedAppUsage
Wink Saville63f03dd2014-10-23 10:44:45 -07002078 public static boolean isLocalEmergencyNumber(Context context, int subId, String number) {
Wink Savillefb40dd42014-06-12 17:02:31 -07002079 return isLocalEmergencyNumberInternal(subId, number,
2080 context,
Yorke Lee8c2d8c02014-06-05 15:47:11 -07002081 true /* useExactMatch */);
David Brown1a811692011-11-08 11:11:56 -08002082 }
2083
2084 /**
2085 * Checks if a given number might *potentially* result in a call to an
2086 * emergency service, for the country that the user is in. The current
2087 * country is determined using the CountryDetector.
2088 *
2089 * Specifically, this method will return true if the specified number
2090 * is an emergency number in the current country, *or* if the number
2091 * simply starts with the same digits as any emergency number for the
2092 * current country.
2093 *
2094 * This method is intended for internal use by the phone app when
2095 * deciding whether to allow ACTION_CALL intents from 3rd party apps
2096 * (where we're required to *not* allow emergency calls to be placed.)
Yorke Lee282129f2014-06-05 08:41:47 -07002097 *
Wink Savillefb40dd42014-06-12 17:02:31 -07002098 * @param number the number to look up.
2099 * @param context the specific context which the number should be checked against
David Brown1a811692011-11-08 11:11:56 -08002100 * @return true if the specified number is an emergency number for a local country, based on the
2101 * CountryDetector.
2102 *
2103 * @see android.location.CountryDetector
sqian46c0c302018-12-27 14:12:11 -08002104 *
sqian3b5f87f2019-02-22 15:54:47 -08002105 * @deprecated Please use {@link TelephonyManager#isPotentialEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08002106 * instead.
2107 *
David Brown1a811692011-11-08 11:11:56 -08002108 * @hide
2109 */
sqian46c0c302018-12-27 14:12:11 -08002110 @Deprecated
Mathew Inwooda8382062018-08-16 17:01:12 +01002111 @UnsupportedAppUsage
Yorke Lee282129f2014-06-05 08:41:47 -07002112 public static boolean isPotentialLocalEmergencyNumber(Context context, String number) {
Wink Savillefb40dd42014-06-12 17:02:31 -07002113 return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number);
2114 }
2115
2116 /**
2117 * Checks if a given number might *potentially* result in a call to an
2118 * emergency service, for the country that the user is in. The current
2119 * country is determined using the CountryDetector.
2120 *
2121 * Specifically, this method will return true if the specified number
2122 * is an emergency number in the current country, *or* if the number
2123 * simply starts with the same digits as any emergency number for the
2124 * current country.
2125 *
2126 * This method is intended for internal use by the phone app when
2127 * deciding whether to allow ACTION_CALL intents from 3rd party apps
2128 * (where we're required to *not* allow emergency calls to be placed.)
2129 *
2130 * @param subId the subscription id of the SIM.
2131 * @param number the number to look up.
2132 * @param context the specific context which the number should be checked against
2133 * @return true if the specified number is an emergency number for a local country, based on the
2134 * CountryDetector.
2135 *
sqian3b5f87f2019-02-22 15:54:47 -08002136 * @deprecated Please use {@link TelephonyManager#isPotentialEmergencyNumber(String)}
sqian46c0c302018-12-27 14:12:11 -08002137 * instead.
2138 *
Wink Savillefb40dd42014-06-12 17:02:31 -07002139 * @hide
2140 */
Mathew Inwooda8382062018-08-16 17:01:12 +01002141 @UnsupportedAppUsage
sqian46c0c302018-12-27 14:12:11 -08002142 @Deprecated
Wink Saville63f03dd2014-10-23 10:44:45 -07002143 public static boolean isPotentialLocalEmergencyNumber(Context context, int subId,
Wink Savillefb40dd42014-06-12 17:02:31 -07002144 String number) {
2145 return isLocalEmergencyNumberInternal(subId, number,
2146 context,
Yorke Lee8c2d8c02014-06-05 15:47:11 -07002147 false /* useExactMatch */);
David Brown1a811692011-11-08 11:11:56 -08002148 }
2149
2150 /**
2151 * Helper function for isLocalEmergencyNumber() and
2152 * isPotentialLocalEmergencyNumber().
Wink Savillefb40dd42014-06-12 17:02:31 -07002153 *
Yorke Lee282129f2014-06-05 08:41:47 -07002154 * @param number the number to look up.
Wink Savillefb40dd42014-06-12 17:02:31 -07002155 * @param context the specific context which the number should be checked against
David Brown1a811692011-11-08 11:11:56 -08002156 * @param useExactMatch if true, consider a number to be an emergency
2157 * number only if it *exactly* matches a number listed in
2158 * the RIL / SIM. If false, a number is considered to be an
2159 * emergency number if it simply starts with the same digits
2160 * as any of the emergency numbers listed in the RIL / SIM.
2161 *
2162 * @return true if the specified number is an emergency number for a
2163 * local country, based on the CountryDetector.
2164 *
2165 * @see android.location.CountryDetector
Wink Savillefb40dd42014-06-12 17:02:31 -07002166 * @hide
David Brown1a811692011-11-08 11:11:56 -08002167 */
Wink Savillefb40dd42014-06-12 17:02:31 -07002168 private static boolean isLocalEmergencyNumberInternal(String number,
2169 Context context,
2170 boolean useExactMatch) {
2171 return isLocalEmergencyNumberInternal(getDefaultVoiceSubId(), number, context,
2172 useExactMatch);
2173 }
2174
2175 /**
2176 * Helper function for isLocalEmergencyNumber() and
2177 * isPotentialLocalEmergencyNumber().
2178 *
2179 * @param subId the subscription id of the SIM.
2180 * @param number the number to look up.
2181 * @param context the specific context which the number should be checked against
2182 * @param useExactMatch if true, consider a number to be an emergency
2183 * number only if it *exactly* matches a number listed in
2184 * the RIL / SIM. If false, a number is considered to be an
2185 * emergency number if it simply starts with the same digits
2186 * as any of the emergency numbers listed in the RIL / SIM.
2187 *
2188 * @return true if the specified number is an emergency number for a
2189 * local country, based on the CountryDetector.
2190 * @hide
2191 */
Wink Saville63f03dd2014-10-23 10:44:45 -07002192 private static boolean isLocalEmergencyNumberInternal(int subId, String number,
Wink Savillefb40dd42014-06-12 17:02:31 -07002193 Context context,
Yorke Lee8c2d8c02014-06-05 15:47:11 -07002194 boolean useExactMatch) {
Hall Liub49a3af2020-01-21 15:34:02 -08002195 return isEmergencyNumberInternal(subId, number, null /* unused */, useExactMatch);
Shaopeng Jia6b7c3f82011-09-14 17:36:18 +02002196 }
2197
2198 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -07002199 * isVoiceMailNumber: checks a given number against the voicemail
2200 * number provided by the RIL and SIM card. The caller must have
2201 * the READ_PHONE_STATE credential.
2202 *
2203 * @param number the number to look up.
2204 * @return true if the number is in the list of voicemail. False
2205 * otherwise, including if the caller does not have the permission
2206 * to read the VM number.
Nicolas Catania60d45f02009-09-15 18:32:02 -07002207 */
2208 public static boolean isVoiceMailNumber(String number) {
Shishir Agrawal3a86d3d2016-01-25 13:03:07 -08002209 return isVoiceMailNumber(SubscriptionManager.getDefaultSubscriptionId(), number);
Wink Savillefb40dd42014-06-12 17:02:31 -07002210 }
2211
2212 /**
2213 * isVoiceMailNumber: checks a given number against the voicemail
2214 * number provided by the RIL and SIM card. The caller must have
2215 * the READ_PHONE_STATE credential.
2216 *
2217 * @param subId the subscription id of the SIM.
2218 * @param number the number to look up.
2219 * @return true if the number is in the list of voicemail. False
2220 * otherwise, including if the caller does not have the permission
2221 * to read the VM number.
2222 * @hide
2223 */
Wink Saville63f03dd2014-10-23 10:44:45 -07002224 public static boolean isVoiceMailNumber(int subId, String number) {
Yorke Lee1249bdb2015-06-30 10:07:40 -07002225 return isVoiceMailNumber(null, subId, number);
2226 }
Nicolas Catania60d45f02009-09-15 18:32:02 -07002227
Yorke Lee1249bdb2015-06-30 10:07:40 -07002228 /**
2229 * isVoiceMailNumber: checks a given number against the voicemail
2230 * number provided by the RIL and SIM card. The caller must have
2231 * the READ_PHONE_STATE credential.
2232 *
Jordan Liu360d5672016-11-09 13:23:42 -08002233 * @param context {@link Context}.
Yorke Lee1249bdb2015-06-30 10:07:40 -07002234 * @param subId the subscription id of the SIM.
2235 * @param number the number to look up.
2236 * @return true if the number is in the list of voicemail. False
2237 * otherwise, including if the caller does not have the permission
2238 * to read the VM number.
2239 * @hide
2240 */
Hall Liud2f962a2019-10-31 15:17:58 -07002241 @SystemApi
2242 @TestApi
2243 public static boolean isVoiceMailNumber(@NonNull Context context, int subId,
2244 @Nullable String number) {
Jordan Liu360d5672016-11-09 13:23:42 -08002245 String vmNumber, mdn;
Nicolas Catania60d45f02009-09-15 18:32:02 -07002246 try {
Yorke Lee1249bdb2015-06-30 10:07:40 -07002247 final TelephonyManager tm;
2248 if (context == null) {
2249 tm = TelephonyManager.getDefault();
Jordan Liu360d5672016-11-09 13:23:42 -08002250 if (DBG) log("isVoiceMailNumber: default tm");
Yorke Lee1249bdb2015-06-30 10:07:40 -07002251 } else {
2252 tm = TelephonyManager.from(context);
Jordan Liu360d5672016-11-09 13:23:42 -08002253 if (DBG) log("isVoiceMailNumber: tm from context");
Yorke Lee1249bdb2015-06-30 10:07:40 -07002254 }
2255 vmNumber = tm.getVoiceMailNumber(subId);
Jordan Liu360d5672016-11-09 13:23:42 -08002256 mdn = tm.getLine1Number(subId);
2257 if (DBG) log("isVoiceMailNumber: mdn=" + mdn + ", vmNumber=" + vmNumber
2258 + ", number=" + number);
Nicolas Catania60d45f02009-09-15 18:32:02 -07002259 } catch (SecurityException ex) {
Jordan Liu360d5672016-11-09 13:23:42 -08002260 if (DBG) log("isVoiceMailNumber: SecurityExcpetion caught");
Nicolas Catania60d45f02009-09-15 18:32:02 -07002261 return false;
2262 }
Nicolas Catania60d45f02009-09-15 18:32:02 -07002263 // Strip the separators from the number before comparing it
2264 // to the list.
Tammo Spalink9e534152009-10-19 14:10:47 +08002265 number = extractNetworkPortionAlt(number);
Jordan Liu360d5672016-11-09 13:23:42 -08002266 if (TextUtils.isEmpty(number)) {
2267 if (DBG) log("isVoiceMailNumber: number is empty after stripping");
2268 return false;
2269 }
Nicolas Catania60d45f02009-09-15 18:32:02 -07002270
Jordan Liu360d5672016-11-09 13:23:42 -08002271 // check if the carrier considers MDN to be an additional voicemail number
2272 boolean compareWithMdn = false;
2273 if (context != null) {
2274 CarrierConfigManager configManager = (CarrierConfigManager)
2275 context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
2276 if (configManager != null) {
2277 PersistableBundle b = configManager.getConfigForSubId(subId);
2278 if (b != null) {
2279 compareWithMdn = b.getBoolean(CarrierConfigManager.
2280 KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL);
2281 if (DBG) log("isVoiceMailNumber: compareWithMdn=" + compareWithMdn);
2282 }
2283 }
2284 }
2285
2286 if (compareWithMdn) {
2287 if (DBG) log("isVoiceMailNumber: treating mdn as additional vm number");
2288 return compare(number, vmNumber) || compare(number, mdn);
2289 } else {
2290 if (DBG) log("isVoiceMailNumber: returning regular compare");
2291 return compare(number, vmNumber);
2292 }
Nicolas Catania60d45f02009-09-15 18:32:02 -07002293 }
2294
2295 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002296 * Translates any alphabetic letters (i.e. [A-Za-z]) in the
2297 * specified phone number into the equivalent numeric digits,
2298 * according to the phone keypad letter mapping described in
2299 * ITU E.161 and ISO/IEC 9995-8.
2300 *
2301 * @return the input string, with alpha letters converted to numeric
2302 * digits using the phone keypad letter mapping. For example,
2303 * an input of "1-800-GOOG-411" will return "1-800-4664-411".
2304 */
2305 public static String convertKeypadLettersToDigits(String input) {
2306 if (input == null) {
2307 return input;
2308 }
2309 int len = input.length();
2310 if (len == 0) {
2311 return input;
2312 }
2313
2314 char[] out = input.toCharArray();
2315
2316 for (int i = 0; i < len; i++) {
2317 char c = out[i];
2318 // If this char isn't in KEYPAD_MAP at all, just leave it alone.
2319 out[i] = (char) KEYPAD_MAP.get(c, c);
2320 }
2321
2322 return new String(out);
2323 }
2324
2325 /**
2326 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.)
2327 * TODO: This should come from a resource.
2328 */
2329 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
2330 static {
2331 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2');
2332 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2');
2333
2334 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3');
2335 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3');
2336
2337 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4');
2338 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4');
2339
2340 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5');
2341 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5');
2342
2343 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6');
2344 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6');
2345
2346 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7');
2347 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7');
2348
2349 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8');
2350 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8');
2351
2352 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9');
2353 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9');
2354 }
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002355
2356 //================ Plus Code formatting =========================
2357 private static final char PLUS_SIGN_CHAR = '+';
2358 private static final String PLUS_SIGN_STRING = "+";
2359 private static final String NANP_IDP_STRING = "011";
2360 private static final int NANP_LENGTH = 10;
2361
2362 /**
2363 * This function checks if there is a plus sign (+) in the passed-in dialing number.
2364 * If there is, it processes the plus sign based on the default telephone
2365 * numbering plan of the system when the phone is activated and the current
2366 * telephone numbering plan of the system that the phone is camped on.
2367 * Currently, we only support the case that the default and current telephone
2368 * numbering plans are North American Numbering Plan(NANP).
2369 *
2370 * The passed-in dialStr should only contain the valid format as described below,
2371 * 1) the 1st character in the dialStr should be one of the really dialable
2372 * characters listed below
2373 * ISO-LATIN characters 0-9, *, # , +
2374 * 2) the dialStr should already strip out the separator characters,
2375 * every character in the dialStr should be one of the non separator characters
2376 * listed below
2377 * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE
2378 *
2379 * Otherwise, this function returns the dial string passed in
2380 *
Libin Tang7850cdd2009-08-18 13:22:47 -05002381 * @param dialStr the original dial string
2382 * @return the converted dial string if the current/default countries belong to NANP,
2383 * and if there is the "+" in the original dial string. Otherwise, the original dial
2384 * string returns.
2385 *
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002386 * This API is for CDMA only
2387 *
2388 * @hide TODO: pending API Council approval
2389 */
Mathew Inwooda8382062018-08-16 17:01:12 +01002390 @UnsupportedAppUsage
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002391 public static String cdmaCheckAndProcessPlusCode(String dialStr) {
2392 if (!TextUtils.isEmpty(dialStr)) {
2393 if (isReallyDialable(dialStr.charAt(0)) &&
2394 isNonSeparator(dialStr)) {
Legler Wu85973dc2015-01-29 15:07:39 +08002395 String currIso = TelephonyManager.getDefault().getNetworkCountryIso();
2396 String defaultIso = TelephonyManager.getDefault().getSimCountryIso();
Libin Tang7850cdd2009-08-18 13:22:47 -05002397 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) {
2398 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr,
2399 getFormatTypeFromCountryCode(currIso),
2400 getFormatTypeFromCountryCode(defaultIso));
2401 }
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002402 }
2403 }
2404 return dialStr;
2405 }
2406
2407 /**
Jake Hamby921bf622013-11-04 15:22:55 -08002408 * Process phone number for CDMA, converting plus code using the home network number format.
2409 * This is used for outgoing SMS messages.
2410 *
2411 * @param dialStr the original dial string
2412 * @return the converted dial string
2413 * @hide for internal use
2414 */
2415 public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) {
2416 if (!TextUtils.isEmpty(dialStr)) {
2417 if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) {
Legler Wu85973dc2015-01-29 15:07:39 +08002418 String defaultIso = TelephonyManager.getDefault().getSimCountryIso();
Jake Hamby921bf622013-11-04 15:22:55 -08002419 if (!TextUtils.isEmpty(defaultIso)) {
2420 int format = getFormatTypeFromCountryCode(defaultIso);
2421 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format);
2422 }
2423 }
2424 }
2425 return dialStr;
2426 }
2427
2428 /**
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002429 * This function should be called from checkAndProcessPlusCode only
2430 * And it is used for test purpose also.
2431 *
2432 * It checks the dial string by looping through the network portion,
2433 * post dial portion 1, post dial porting 2, etc. If there is any
2434 * plus sign, then process the plus sign.
2435 * Currently, this function supports the plus sign conversion within NANP only.
2436 * Specifically, it handles the plus sign in the following ways:
Libin Tang7850cdd2009-08-18 13:22:47 -05002437 * 1)+1NANP,remove +, e.g.
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002438 * +18475797000 is converted to 18475797000,
Libin Tang7850cdd2009-08-18 13:22:47 -05002439 * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g,
2440 * +8475797000 is converted to 0118475797000,
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002441 * +11875767800 is converted to 01111875767800
Libin Tang7850cdd2009-08-18 13:22:47 -05002442 * 3)+1NANP in post dial string(s), e.g.
2443 * 8475797000;+18475231753 is converted to 8475797000;18475231753
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002444 *
Libin Tang7850cdd2009-08-18 13:22:47 -05002445 *
2446 * @param dialStr the original dial string
2447 * @param currFormat the numbering system of the current country that the phone is camped on
2448 * @param defaultFormat the numbering system of the country that the phone is activated on
2449 * @return the converted dial string if the current/default countries belong to NANP,
2450 * and if there is the "+" in the original dial string. Otherwise, the original dial
2451 * string returns.
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002452 *
2453 * @hide
2454 */
Libin Tang7850cdd2009-08-18 13:22:47 -05002455 public static String
Jake Hamby145ff602010-04-15 15:23:12 -07002456 cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) {
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002457 String retStr = dialStr;
2458
Steven Liu9b656ee2014-07-10 14:10:50 -05002459 boolean useNanp = (currFormat == defaultFormat) && (currFormat == FORMAT_NANP);
2460
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002461 // Checks if the plus sign character is in the passed-in dial string
2462 if (dialStr != null &&
2463 dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
2464
Steven Liu9b656ee2014-07-10 14:10:50 -05002465 // Handle case where default and current telephone numbering plans are NANP.
2466 String postDialStr = null;
2467 String tempDialStr = dialStr;
2468
2469 // Sets the retStr to null since the conversion will be performed below.
2470 retStr = null;
2471 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr);
2472 // This routine is to process the plus sign in the dial string by loop through
2473 // the network portion, post dial portion 1, post dial portion 2... etc. if
2474 // applied
2475 do {
2476 String networkDialStr;
2477 // Format the string based on the rules for the country the number is from,
2478 // and the current country the phone is camped
2479 if (useNanp) {
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002480 networkDialStr = extractNetworkPortion(tempDialStr);
Steven Liu9b656ee2014-07-10 14:10:50 -05002481 } else {
2482 networkDialStr = extractNetworkPortionAlt(tempDialStr);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002483
Steven Liu9b656ee2014-07-10 14:10:50 -05002484 }
2485
2486 networkDialStr = processPlusCode(networkDialStr, useNanp);
2487
2488 // Concatenates the string that is converted from network portion
2489 if (!TextUtils.isEmpty(networkDialStr)) {
2490 if (retStr == null) {
2491 retStr = networkDialStr;
Libin Tang7850cdd2009-08-18 13:22:47 -05002492 } else {
Steven Liu9b656ee2014-07-10 14:10:50 -05002493 retStr = retStr.concat(networkDialStr);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002494 }
Steven Liu9b656ee2014-07-10 14:10:50 -05002495 } else {
2496 // This should never happen since we checked the if dialStr is null
2497 // and if it contains the plus sign in the beginning of this function.
2498 // The plus sign is part of the network portion.
2499 Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr);
2500 return dialStr;
2501 }
2502 postDialStr = extractPostDialPortion(tempDialStr);
2503 if (!TextUtils.isEmpty(postDialStr)) {
2504 int dialableIndex = findDialableIndexFromPostDialStr(postDialStr);
Libin Tang7850cdd2009-08-18 13:22:47 -05002505
Steven Liu9b656ee2014-07-10 14:10:50 -05002506 // dialableIndex should always be greater than 0
2507 if (dialableIndex >= 1) {
2508 retStr = appendPwCharBackToOrigDialStr(dialableIndex,
2509 retStr,postDialStr);
2510 // Skips the P/W character, extracts the dialable portion
2511 tempDialStr = postDialStr.substring(dialableIndex);
2512 } else {
2513 // Non-dialable character such as P/W should not be at the end of
Jack Yu67140302015-12-10 12:27:58 -08002514 // the dial string after P/W processing in GsmCdmaConnection.java
Steven Liu9b656ee2014-07-10 14:10:50 -05002515 // Set the postDialStr to "" to break out of the loop
2516 if (dialableIndex < 0) {
2517 postDialStr = "";
Libin Tang7850cdd2009-08-18 13:22:47 -05002518 }
Steven Liu9b656ee2014-07-10 14:10:50 -05002519 Rlog.e("wrong postDialStr=", postDialStr);
Libin Tang7850cdd2009-08-18 13:22:47 -05002520 }
Steven Liu9b656ee2014-07-10 14:10:50 -05002521 }
2522 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr);
2523 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr));
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002524 }
2525 return retStr;
Steven Liu9b656ee2014-07-10 14:10:50 -05002526 }
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002527
Ihab Awad0855e7a2014-12-05 13:38:11 -08002528 /**
2529 * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as
2530 * containing a phone number in its entirety.
2531 *
2532 * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number.
2533 * @return A {@code CharSequence} with appropriate annotations.
Ihab Awad0855e7a2014-12-05 13:38:11 -08002534 */
Brian Attwell115c0442015-06-02 13:27:47 -07002535 public static CharSequence createTtsSpannable(CharSequence phoneNumber) {
Ihab Awad0855e7a2014-12-05 13:38:11 -08002536 if (phoneNumber == null) {
2537 return null;
2538 }
2539 Spannable spannable = Spannable.Factory.getInstance().newSpannable(phoneNumber);
Brian Attwell115c0442015-06-02 13:27:47 -07002540 PhoneNumberUtils.addTtsSpan(spannable, 0, spannable.length());
Ihab Awad0855e7a2014-12-05 13:38:11 -08002541 return spannable;
2542 }
2543
2544 /**
2545 * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location,
2546 * annotating that location as containing a phone number.
2547 *
2548 * @param s A {@code Spannable} to annotate.
2549 * @param start The starting character position of the phone number in {@code s}.
Brian Attwell1a15ab92015-05-28 16:23:37 -07002550 * @param endExclusive The position after the ending character in the phone number {@code s}.
Ihab Awad0855e7a2014-12-05 13:38:11 -08002551 */
Brian Attwell115c0442015-06-02 13:27:47 -07002552 public static void addTtsSpan(Spannable s, int start, int endExclusive) {
2553 s.setSpan(createTtsSpan(s.subSequence(start, endExclusive).toString()),
Ihab Awad0855e7a2014-12-05 13:38:11 -08002554 start,
Brian Attwell1a15ab92015-05-28 16:23:37 -07002555 endExclusive,
Ihab Awad0855e7a2014-12-05 13:38:11 -08002556 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2557 }
2558
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002559 /**
2560 * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as
2561 * containing a phone number in its entirety.
2562 *
2563 * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number.
2564 * @return A {@code CharSequence} with appropriate annotations.
Brian Attwell115c0442015-06-02 13:27:47 -07002565 * @deprecated Renamed {@link #createTtsSpannable}.
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002566 *
2567 * @hide
2568 */
2569 @Deprecated
Mathew Inwooda8382062018-08-16 17:01:12 +01002570 @UnsupportedAppUsage
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002571 public static CharSequence ttsSpanAsPhoneNumber(CharSequence phoneNumber) {
Brian Attwell115c0442015-06-02 13:27:47 -07002572 return createTtsSpannable(phoneNumber);
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002573 }
2574
2575 /**
2576 * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location,
2577 * annotating that location as containing a phone number.
2578 *
2579 * @param s A {@code Spannable} to annotate.
2580 * @param start The starting character position of the phone number in {@code s}.
2581 * @param end The ending character position of the phone number in {@code s}.
2582 *
Brian Attwell115c0442015-06-02 13:27:47 -07002583 * @deprecated Renamed {@link #addTtsSpan}.
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002584 *
2585 * @hide
2586 */
2587 @Deprecated
2588 public static void ttsSpanAsPhoneNumber(Spannable s, int start, int end) {
Brian Attwell115c0442015-06-02 13:27:47 -07002589 addTtsSpan(s, start, end);
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002590 }
2591
2592 /**
2593 * Create a {@code TtsSpan} for the supplied {@code String}.
2594 *
2595 * @param phoneNumberString A {@code String} the entirety of which represents a phone number.
2596 * @return A {@code TtsSpan} for {@param phoneNumberString}.
2597 */
Brian Attwell115c0442015-06-02 13:27:47 -07002598 public static TtsSpan createTtsSpan(String phoneNumberString) {
Brian Attwellc6dbe3b2015-02-04 10:38:12 -08002599 if (phoneNumberString == null) {
2600 return null;
2601 }
2602
2603 // Parse the phone number
2604 final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
2605 PhoneNumber phoneNumber = null;
2606 try {
2607 // Don't supply a defaultRegion so this fails for non-international numbers because
2608 // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already
2609 // present
2610 phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null);
2611 } catch (NumberParseException ignored) {
2612 }
2613
2614 // Build a telephone tts span
2615 final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder();
2616 if (phoneNumber == null) {
2617 // Strip separators otherwise TalkBack will be silent
2618 // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel)
2619 builder.setNumberParts(splitAtNonNumerics(phoneNumberString));
2620 } else {
2621 if (phoneNumber.hasCountryCode()) {
2622 builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode()));
2623 }
2624 builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber()));
2625 }
2626 return builder.build();
2627 }
2628
Ihab Awad0855e7a2014-12-05 13:38:11 -08002629 // Split a phone number like "+20(123)-456#" using spaces, ignoring anything that is not
Tyler Gunnc78a6152017-09-05 14:28:47 -07002630 // a digit or the characters * and #, to produce a result like "20 123 456#".
Ihab Awad0855e7a2014-12-05 13:38:11 -08002631 private static String splitAtNonNumerics(CharSequence number) {
2632 StringBuilder sb = new StringBuilder(number.length());
2633 for (int i = 0; i < number.length(); i++) {
Tyler Gunnc78a6152017-09-05 14:28:47 -07002634 sb.append(PhoneNumberUtils.is12Key(number.charAt(i))
Ihab Awad0855e7a2014-12-05 13:38:11 -08002635 ? number.charAt(i)
2636 : " ");
2637 }
2638 // It is very important to remove extra spaces. At time of writing, any leading or trailing
2639 // spaces, or any sequence of more than one space, will confuse TalkBack and cause the TTS
2640 // span to be non-functional!
2641 return sb.toString().replaceAll(" +", " ").trim();
2642 }
2643
Steven Liu9b656ee2014-07-10 14:10:50 -05002644 private static String getCurrentIdp(boolean useNanp) {
sohryun.shina423a272015-01-12 13:42:59 +09002645 String ps = null;
Robert Greenwalt3c6e38d2015-01-21 19:52:20 +00002646 if (useNanp) {
sohryun.shina423a272015-01-12 13:42:59 +09002647 ps = NANP_IDP_STRING;
Robert Greenwalt3c6e38d2015-01-21 19:52:20 +00002648 } else {
sohryun.shina423a272015-01-12 13:42:59 +09002649 // in case, there is no IDD is found, we shouldn't convert it.
Inseob Kima9d6cd42019-11-08 15:06:37 +09002650 ps = TelephonyProperties.operator_idp_string().orElse(PLUS_SIGN_STRING);
sohryun.shina423a272015-01-12 13:42:59 +09002651 }
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002652 return ps;
2653 }
2654
2655 private static boolean isTwoToNine (char c) {
2656 if (c >= '2' && c <= '9') {
2657 return true;
2658 } else {
2659 return false;
2660 }
2661 }
2662
Libin Tang7850cdd2009-08-18 13:22:47 -05002663 private static int getFormatTypeFromCountryCode (String country) {
2664 // Check for the NANP countries
2665 int length = NANP_COUNTRIES.length;
2666 for (int i = 0; i < length; i++) {
2667 if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) {
2668 return FORMAT_NANP;
2669 }
2670 }
2671 if ("jp".compareToIgnoreCase(country) == 0) {
2672 return FORMAT_JAPAN;
2673 }
2674 return FORMAT_UNKNOWN;
2675 }
2676
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002677 /**
2678 * This function checks if the passed in string conforms to the NANP format
2679 * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9
Xia Ying77ea3ee2014-07-21 20:24:56 -05002680 * @hide
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002681 */
Mathew Inwooda8382062018-08-16 17:01:12 +01002682 @UnsupportedAppUsage
Xia Ying77ea3ee2014-07-21 20:24:56 -05002683 public static boolean isNanp (String dialStr) {
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002684 boolean retVal = false;
2685 if (dialStr != null) {
2686 if (dialStr.length() == NANP_LENGTH) {
2687 if (isTwoToNine(dialStr.charAt(0)) &&
2688 isTwoToNine(dialStr.charAt(3))) {
2689 retVal = true;
2690 for (int i=1; i<NANP_LENGTH; i++ ) {
2691 char c=dialStr.charAt(i);
2692 if (!PhoneNumberUtils.isISODigit(c)) {
2693 retVal = false;
2694 break;
2695 }
2696 }
2697 }
2698 }
2699 } else {
Wink Saville22b1e802012-12-07 10:26:41 -08002700 Rlog.e("isNanp: null dialStr passed in", dialStr);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002701 }
2702 return retVal;
2703 }
2704
2705 /**
2706 * This function checks if the passed in string conforms to 1-NANP format
2707 */
2708 private static boolean isOneNanp(String dialStr) {
2709 boolean retVal = false;
2710 if (dialStr != null) {
2711 String newDialStr = dialStr.substring(1);
2712 if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) {
2713 retVal = true;
2714 }
2715 } else {
Wink Saville22b1e802012-12-07 10:26:41 -08002716 Rlog.e("isOneNanp: null dialStr passed in", dialStr);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002717 }
2718 return retVal;
2719 }
2720
2721 /**
David Brownd07833f2010-10-08 12:14:48 -07002722 * Determines if the specified number is actually a URI
2723 * (i.e. a SIP address) rather than a regular PSTN phone number,
2724 * based on whether or not the number contains an "@" character.
2725 *
John Wangc159c842010-08-11 01:27:49 -07002726 * @hide
2727 * @param number
2728 * @return true if number contains @
2729 */
Hall Liud2f962a2019-10-31 15:17:58 -07002730 @SystemApi
2731 @TestApi
2732 public static boolean isUriNumber(@Nullable String number) {
David Brownd07833f2010-10-08 12:14:48 -07002733 // Note we allow either "@" or "%40" to indicate a URI, in case
2734 // the passed-in string is URI-escaped. (Neither "@" nor "%40"
2735 // will ever be found in a legal PSTN number.)
2736 return number != null && (number.contains("@") || number.contains("%40"));
John Wangc159c842010-08-11 01:27:49 -07002737 }
2738
2739 /**
David Brown158f1162011-11-16 22:10:56 -08002740 * @return the "username" part of the specified SIP address,
2741 * i.e. the part before the "@" character (or "%40").
2742 *
2743 * @param number SIP address of the form "username@domainname"
2744 * (or the URI-escaped equivalent "username%40domainname")
Ben Gilad95cde2d2015-01-08 17:56:28 -08002745 * @see #isUriNumber
David Brown158f1162011-11-16 22:10:56 -08002746 *
2747 * @hide
2748 */
Hall Liud2f962a2019-10-31 15:17:58 -07002749 @SystemApi
2750 @TestApi
2751 public static @NonNull String getUsernameFromUriNumber(@NonNull String number) {
David Brown158f1162011-11-16 22:10:56 -08002752 // The delimiter between username and domain name can be
2753 // either "@" or "%40" (the URI-escaped equivalent.)
2754 int delimiterIndex = number.indexOf('@');
2755 if (delimiterIndex < 0) {
2756 delimiterIndex = number.indexOf("%40");
2757 }
2758 if (delimiterIndex < 0) {
Wink Saville22b1e802012-12-07 10:26:41 -08002759 Rlog.w(LOG_TAG,
David Brown158f1162011-11-16 22:10:56 -08002760 "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'");
2761 delimiterIndex = number.length();
2762 }
2763 return number.substring(0, delimiterIndex);
2764 }
2765
2766 /**
Tyler Gunn825da232016-05-10 20:04:27 -07002767 * Given a {@link Uri} with a {@code sip} scheme, attempts to build an equivalent {@code tel}
2768 * scheme {@link Uri}. If the source {@link Uri} does not contain a valid number, or is not
2769 * using the {@code sip} scheme, the original {@link Uri} is returned.
2770 *
2771 * @param source The {@link Uri} to convert.
2772 * @return The equivalent {@code tel} scheme {@link Uri}.
2773 *
2774 * @hide
2775 */
2776 public static Uri convertSipUriToTelUri(Uri source) {
2777 // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
2778 // Per RFC3261, the "user" can be a telephone number.
2779 // For example: sip:1650555121;phone-context=blah.com@host.com
2780 // In this case, the phone number is in the user field of the URI, and the parameters can be
2781 // ignored.
2782 //
2783 // A SIP URI can also specify a phone number in a format similar to:
2784 // sip:+1-212-555-1212@something.com;user=phone
2785 // In this case, the phone number is again in user field and the parameters can be ignored.
2786 // We can get the user field in these instances by splitting the string on the @, ;, or :
2787 // and looking at the first found item.
2788
2789 String scheme = source.getScheme();
2790
2791 if (!PhoneAccount.SCHEME_SIP.equals(scheme)) {
2792 // Not a sip URI, bail.
2793 return source;
2794 }
2795
2796 String number = source.getSchemeSpecificPart();
2797 String numberParts[] = number.split("[@;:]");
2798
2799 if (numberParts.length == 0) {
2800 // Number not found, bail.
2801 return source;
2802 }
2803 number = numberParts[0];
2804
2805 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
2806 }
2807
2808 /**
Steven Liu9b656ee2014-07-10 14:10:50 -05002809 * This function handles the plus code conversion
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002810 * If the number format is
Libin Tang7850cdd2009-08-18 13:22:47 -05002811 * 1)+1NANP,remove +,
2812 * 2)other than +1NANP, any + numbers,replace + with the current IDP
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002813 */
Steven Liu9b656ee2014-07-10 14:10:50 -05002814 private static String processPlusCode(String networkDialStr, boolean useNanp) {
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002815 String retStr = networkDialStr;
2816
Steven Liu9b656ee2014-07-10 14:10:50 -05002817 if (DBG) log("processPlusCode, networkDialStr = " + networkDialStr
2818 + "for NANP = " + useNanp);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002819 // If there is a plus sign at the beginning of the dial string,
2820 // Convert the plus sign to the default IDP since it's an international number
Kenny Root7ae17762010-02-17 09:55:53 -08002821 if (networkDialStr != null &&
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002822 networkDialStr.charAt(0) == PLUS_SIGN_CHAR &&
2823 networkDialStr.length() > 1) {
2824 String newStr = networkDialStr.substring(1);
Steven Liu9b656ee2014-07-10 14:10:50 -05002825 // TODO: for nonNanp, should the '+' be removed if following number is country code
2826 if (useNanp && isOneNanp(newStr)) {
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002827 // Remove the leading plus sign
2828 retStr = newStr;
Steven Liu9b656ee2014-07-10 14:10:50 -05002829 } else {
2830 // Replaces the plus sign with the default IDP
2831 retStr = networkDialStr.replaceFirst("[+]", getCurrentIdp(useNanp));
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002832 }
2833 }
Steven Liu9b656ee2014-07-10 14:10:50 -05002834 if (DBG) log("processPlusCode, retStr=" + retStr);
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002835 return retStr;
2836 }
2837
2838 // This function finds the index of the dialable character(s)
2839 // in the post dial string
2840 private static int findDialableIndexFromPostDialStr(String postDialStr) {
2841 for (int index = 0;index < postDialStr.length();index++) {
2842 char c = postDialStr.charAt(index);
2843 if (isReallyDialable(c)) {
2844 return index;
2845 }
2846 }
2847 return -1;
2848 }
2849
Jake Hamby145ff602010-04-15 15:23:12 -07002850 // This function appends the non-dialable P/W character to the original
Tang@Motorola.com18e7b982009-08-03 18:06:04 -05002851 // dial string based on the dialable index passed in
2852 private static String
2853 appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) {
2854 String retStr;
2855
2856 // There is only 1 P/W character before the dialable characters
2857 if (dialableIndex == 1) {
2858 StringBuilder ret = new StringBuilder(origStr);
2859 ret = ret.append(dialStr.charAt(0));
2860 retStr = ret.toString();
2861 } else {
2862 // It means more than 1 P/W characters in the post dial string,
2863 // appends to retStr
2864 String nonDigitStr = dialStr.substring(0,dialableIndex);
2865 retStr = origStr.concat(nonDigitStr);
2866 }
2867 return retStr;
2868 }
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07002869
Jake Hamby145ff602010-04-15 15:23:12 -07002870 //===== Beginning of utility methods used in compareLoosely() =====
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07002871
2872 /**
2873 * Phone numbers are stored in "lookup" form in the database
2874 * as reversed strings to allow for caller ID lookup
2875 *
2876 * This method takes a phone number and makes a valid SQL "LIKE"
2877 * string that will match the lookup form
2878 *
2879 */
2880 /** all of a up to len must be an international prefix or
2881 * separators/non-dialing digits
2882 */
2883 private static boolean
2884 matchIntlPrefix(String a, int len) {
2885 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
2886 /* 0 1 2 3 45 */
2887
2888 int state = 0;
2889 for (int i = 0 ; i < len ; i++) {
2890 char c = a.charAt(i);
2891
2892 switch (state) {
2893 case 0:
2894 if (c == '+') state = 1;
2895 else if (c == '0') state = 2;
2896 else if (isNonSeparator(c)) return false;
2897 break;
2898
2899 case 2:
2900 if (c == '0') state = 3;
2901 else if (c == '1') state = 4;
2902 else if (isNonSeparator(c)) return false;
2903 break;
2904
2905 case 4:
2906 if (c == '1') state = 5;
2907 else if (isNonSeparator(c)) return false;
2908 break;
2909
2910 default:
2911 if (isNonSeparator(c)) return false;
2912 break;
2913
2914 }
2915 }
2916
2917 return state == 1 || state == 3 || state == 5;
2918 }
2919
2920 /** all of 'a' up to len must be a (+|00|011)country code)
2921 * We're fast and loose with the country code. Any \d{1,3} matches */
2922 private static boolean
2923 matchIntlPrefixAndCC(String a, int len) {
2924 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
2925 /* 0 1 2 3 45 6 7 8 */
2926
2927 int state = 0;
2928 for (int i = 0 ; i < len ; i++ ) {
2929 char c = a.charAt(i);
2930
2931 switch (state) {
2932 case 0:
2933 if (c == '+') state = 1;
2934 else if (c == '0') state = 2;
2935 else if (isNonSeparator(c)) return false;
2936 break;
2937
2938 case 2:
2939 if (c == '0') state = 3;
2940 else if (c == '1') state = 4;
2941 else if (isNonSeparator(c)) return false;
2942 break;
2943
2944 case 4:
2945 if (c == '1') state = 5;
2946 else if (isNonSeparator(c)) return false;
2947 break;
2948
2949 case 1:
2950 case 3:
2951 case 5:
2952 if (isISODigit(c)) state = 6;
2953 else if (isNonSeparator(c)) return false;
2954 break;
2955
2956 case 6:
2957 case 7:
2958 if (isISODigit(c)) state++;
2959 else if (isNonSeparator(c)) return false;
2960 break;
2961
2962 default:
2963 if (isNonSeparator(c)) return false;
2964 }
2965 }
2966
2967 return state == 6 || state == 7 || state == 8;
2968 }
2969
2970 /** all of 'a' up to len must match non-US trunk prefix ('0') */
2971 private static boolean
2972 matchTrunkPrefix(String a, int len) {
2973 boolean found;
2974
2975 found = false;
2976
2977 for (int i = 0 ; i < len ; i++) {
2978 char c = a.charAt(i);
2979
2980 if (c == '0' && !found) {
2981 found = true;
2982 } else if (isNonSeparator(c)) {
2983 return false;
2984 }
2985 }
2986
2987 return found;
2988 }
2989
2990 //===== End of utility methods used only in compareLoosely() =====
2991
Jake Hamby145ff602010-04-15 15:23:12 -07002992 //===== Beginning of utility methods used only in compareStrictly() ====
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07002993
2994 /*
2995 * If true, the number is country calling code.
2996 */
Jake Hamby145ff602010-04-15 15:23:12 -07002997 private static final boolean COUNTRY_CALLING_CALL[] = {
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07002998 true, true, false, false, false, false, false, true, false, false,
2999 false, false, false, false, false, false, false, false, false, false,
3000 true, false, false, false, false, false, false, true, true, false,
3001 true, true, true, true, true, false, true, false, false, true,
3002 true, false, false, true, true, true, true, true, true, true,
3003 false, true, true, true, true, true, true, true, true, false,
3004 true, true, true, true, true, true, true, false, false, false,
3005 false, false, false, false, false, false, false, false, false, false,
3006 false, true, true, true, true, false, true, false, false, true,
3007 true, true, true, true, true, true, false, false, true, false,
3008 };
Jake Hamby145ff602010-04-15 15:23:12 -07003009 private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length;
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07003010
3011 /**
3012 * @return true when input is valid Country Calling Code.
3013 */
3014 private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
3015 return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
Jake Hamby145ff602010-04-15 15:23:12 -07003016 COUNTRY_CALLING_CALL[countryCallingCodeCandidate];
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07003017 }
3018
3019 /**
Jake Hamby145ff602010-04-15 15:23:12 -07003020 * Returns integer corresponding to the input if input "ch" is
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07003021 * ISO-LATIN characters 0-9.
3022 * Returns -1 otherwise
3023 */
3024 private static int tryGetISODigit(char ch) {
3025 if ('0' <= ch && ch <= '9') {
3026 return ch - '0';
3027 } else {
3028 return -1;
3029 }
3030 }
3031
3032 private static class CountryCallingCodeAndNewIndex {
3033 public final int countryCallingCode;
3034 public final int newIndex;
3035 public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) {
3036 this.countryCallingCode = countryCode;
3037 this.newIndex = newIndex;
3038 }
3039 }
3040
3041 /*
3042 * Note that this function does not strictly care the country calling code with
3043 * 3 length (like Morocco: +212), assuming it is enough to use the first two
3044 * digit to compare two phone numbers.
3045 */
3046 private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex(
3047 String str, boolean acceptThailandCase) {
3048 // Rough regexp:
3049 // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
3050 // 0 1 2 3 45 6 7 89
3051 //
3052 // In all the states, this function ignores separator characters.
3053 // "166" is the special case for the call from Thailand to the US. Uguu!
3054 int state = 0;
3055 int ccc = 0;
3056 final int length = str.length();
3057 for (int i = 0 ; i < length ; i++ ) {
3058 char ch = str.charAt(i);
3059 switch (state) {
3060 case 0:
3061 if (ch == '+') state = 1;
3062 else if (ch == '0') state = 2;
3063 else if (ch == '1') {
3064 if (acceptThailandCase) {
3065 state = 8;
3066 } else {
3067 return null;
3068 }
3069 } else if (isDialable(ch)) {
3070 return null;
3071 }
3072 break;
3073
3074 case 2:
3075 if (ch == '0') state = 3;
3076 else if (ch == '1') state = 4;
3077 else if (isDialable(ch)) {
3078 return null;
3079 }
3080 break;
3081
3082 case 4:
3083 if (ch == '1') state = 5;
3084 else if (isDialable(ch)) {
3085 return null;
3086 }
3087 break;
3088
3089 case 1:
3090 case 3:
3091 case 5:
3092 case 6:
3093 case 7:
3094 {
3095 int ret = tryGetISODigit(ch);
3096 if (ret > 0) {
3097 ccc = ccc * 10 + ret;
3098 if (ccc >= 100 || isCountryCallingCode(ccc)) {
3099 return new CountryCallingCodeAndNewIndex(ccc, i + 1);
3100 }
3101 if (state == 1 || state == 3 || state == 5) {
3102 state = 6;
3103 } else {
3104 state++;
3105 }
3106 } else if (isDialable(ch)) {
3107 return null;
3108 }
3109 }
3110 break;
3111 case 8:
3112 if (ch == '6') state = 9;
3113 else if (isDialable(ch)) {
3114 return null;
3115 }
3116 break;
3117 case 9:
3118 if (ch == '6') {
3119 return new CountryCallingCodeAndNewIndex(66, i + 1);
3120 } else {
3121 return null;
3122 }
3123 default:
3124 return null;
3125 }
3126 }
3127
3128 return null;
3129 }
3130
3131 /**
3132 * Currently this function simply ignore the first digit assuming it is
3133 * trunk prefix. Actually trunk prefix is different in each country.
3134 *
3135 * e.g.
3136 * "+79161234567" equals "89161234567" (Russian trunk digit is 8)
3137 * "+33123456789" equals "0123456789" (French trunk digit is 0)
3138 *
3139 */
3140 private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) {
3141 int length = str.length();
3142 for (int i = currentIndex ; i < length ; i++) {
3143 final char ch = str.charAt(i);
3144 if (tryGetISODigit(ch) >= 0) {
3145 return i + 1;
3146 } else if (isDialable(ch)) {
3147 return -1;
3148 }
3149 }
3150 return -1;
3151 }
3152
3153 /**
3154 * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
Jake Hamby145ff602010-04-15 15:23:12 -07003155 * that "str" has only one digit and separator characters. The one digit is
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07003156 * assumed to be trunk prefix.
3157 */
3158 private static boolean checkPrefixIsIgnorable(final String str,
3159 int forwardIndex, int backwardIndex) {
3160 boolean trunk_prefix_was_read = false;
3161 while (backwardIndex >= forwardIndex) {
3162 if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) {
3163 if (trunk_prefix_was_read) {
3164 // More than one digit appeared, meaning that "a" and "b"
3165 // is different.
3166 return false;
3167 } else {
3168 // Ignore just one digit, assuming it is trunk prefix.
3169 trunk_prefix_was_read = true;
3170 }
3171 } else if (isDialable(str.charAt(backwardIndex))) {
3172 // Trunk prefix is a digit, not "*", "#"...
3173 return false;
3174 }
3175 backwardIndex--;
3176 }
3177
3178 return true;
3179 }
3180
Wink Savillefb40dd42014-06-12 17:02:31 -07003181 /**
3182 * Returns Default voice subscription Id.
3183 */
Wink Saville63f03dd2014-10-23 10:44:45 -07003184 private static int getDefaultVoiceSubId() {
Shishir Agrawal3a86d3d2016-01-25 13:03:07 -08003185 return SubscriptionManager.getDefaultVoiceSubscriptionId();
Wink Savillefb40dd42014-06-12 17:02:31 -07003186 }
Daisuke Miyakawa9a24bc562009-09-19 14:25:06 -07003187 //==== End of utility methods used only in compareStrictly() =====
Wei Huang20c747c2016-02-03 10:43:25 +08003188
3189
3190 /*
3191 * The config held calling number conversion map, expected to convert to emergency number.
3192 */
Wei Huang3384c3882017-04-21 18:59:31 +09003193 private static String[] sConvertToEmergencyMap = null;
Wei Huang20c747c2016-02-03 10:43:25 +08003194
3195 /**
3196 * Converts to emergency number based on the conversion map.
3197 * The conversion map is declared as config_convert_to_emergency_number_map.
3198 *
Wei Huang3384c3882017-04-21 18:59:31 +09003199 * @param context a context to use for accessing resources
Wei Huang20c747c2016-02-03 10:43:25 +08003200 * @return The converted emergency number if the number matches conversion map,
3201 * otherwise original number.
3202 *
3203 * @hide
3204 */
Wei Huang3384c3882017-04-21 18:59:31 +09003205 public static String convertToEmergencyNumber(Context context, String number) {
3206 if (context == null || TextUtils.isEmpty(number)) {
Wei Huang20c747c2016-02-03 10:43:25 +08003207 return number;
3208 }
3209
3210 String normalizedNumber = normalizeNumber(number);
3211
3212 // The number is already emergency number. Skip conversion.
3213 if (isEmergencyNumber(normalizedNumber)) {
3214 return number;
3215 }
3216
Wei Huang3384c3882017-04-21 18:59:31 +09003217 if (sConvertToEmergencyMap == null) {
3218 sConvertToEmergencyMap = context.getResources().getStringArray(
3219 com.android.internal.R.array.config_convert_to_emergency_number_map);
3220 }
3221
3222 // The conversion map is not defined (this is default). Skip conversion.
3223 if (sConvertToEmergencyMap == null || sConvertToEmergencyMap.length == 0 ) {
3224 return number;
3225 }
3226
3227 for (String convertMap : sConvertToEmergencyMap) {
Wei Huang20c747c2016-02-03 10:43:25 +08003228 if (DBG) log("convertToEmergencyNumber: " + convertMap);
3229 String[] entry = null;
3230 String[] filterNumbers = null;
3231 String convertedNumber = null;
3232 if (!TextUtils.isEmpty(convertMap)) {
3233 entry = convertMap.split(":");
3234 }
3235 if (entry != null && entry.length == 2) {
3236 convertedNumber = entry[1];
3237 if (!TextUtils.isEmpty(entry[0])) {
3238 filterNumbers = entry[0].split(",");
3239 }
3240 }
3241 // Skip if the format of entry is invalid
3242 if (TextUtils.isEmpty(convertedNumber) || filterNumbers == null
3243 || filterNumbers.length == 0) {
3244 continue;
3245 }
3246
3247 for (String filterNumber : filterNumbers) {
3248 if (DBG) log("convertToEmergencyNumber: filterNumber = " + filterNumber
3249 + ", convertedNumber = " + convertedNumber);
3250 if (!TextUtils.isEmpty(filterNumber) && filterNumber.equals(normalizedNumber)) {
3251 if (DBG) log("convertToEmergencyNumber: Matched. Successfully converted to: "
3252 + convertedNumber);
3253 return convertedNumber;
3254 }
3255 }
3256 }
3257 return number;
3258 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003259}