package com.moez.QKSMS.common.utils; import android.annotation.SuppressLint; import android.text.TextUtils; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; public class PhoneNumberUtils extends android.telephony.PhoneNumberUtils { // Three and four digit phone numbers for either special services, // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should // not match. // // This constant used to be 5, but SMS short codes has increased in length and // can be easily 6 digits now days. Most countries have SMS short code length between // 3 to 6 digits. The exceptions are // // Australia: Short codes are six or eight digits in length, starting with the prefix "19" // followed by an additional four or six digits and two. // Czech Republic: Codes are seven digits in length for MO and five (not billed) or // eight (billed) for MT direction // // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference // // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match // to 7. static final int MIN_MATCH = 7; /** * Format the phone number only if the given number hasn't been formatted. * <p> * The number which has only dailable character is treated as not being * formatted. * * @param phoneNumber * the number to be formatted. * @param phoneNumberE164 * the E164 format number whose country code is used if the given * phoneNumber doesn't have the country code. * @param defaultCountryIso * the ISO 3166-1 two letters country code whose convention will * be used if the phoneNumberE164 is null or invalid, or if phoneNumber * contains IDD. * @return the formatted number if the given number has been formatted, * otherwise, return the given number. * */ @SuppressLint("Override") public static String formatNumber(String phoneNumber, String phoneNumberE164, String defaultCountryIso) { int len = phoneNumber.length(); for (int i = 0; i < len; i++) { if (!isDialable(phoneNumber.charAt(i))) { return phoneNumber; } } PhoneNumberUtil util = PhoneNumberUtil.getInstance(); // Get the country code from phoneNumberE164 if (phoneNumberE164 != null && phoneNumberE164.length() >= 2 && phoneNumberE164.charAt(0) == '+') { try { // The number to be parsed is in E164 format, so the default region used doesn't // matter. Phonenumber.PhoneNumber pn = util.parse(phoneNumberE164, "ZZ"); String regionCode = util.getRegionCodeForNumber(pn); if (!TextUtils.isEmpty(regionCode) && // This makes sure phoneNumber doesn't contain an IDD normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) { defaultCountryIso = regionCode; } } catch (NumberParseException e) { } } String result = formatNumber(phoneNumber, defaultCountryIso); return result != null ? result : phoneNumber; } /** * Replace arabic/unicode digits with decimal digits. * @param number * the number to be normalized. * @return the replaced number. * */ @SuppressLint("Override") public static String replaceUnicodeDigits(String number) { StringBuilder normalizedDigits = new StringBuilder(number.length()); for (char c : number.toCharArray()) { int digit = Character.digit(c, 10); if (digit != -1) { normalizedDigits.append(digit); } else { normalizedDigits.append(c); } } return normalizedDigits.toString(); } /** * Format a phone number. * <p> * If the given number doesn't have the country code, the phone will be * formatted to the default country's convention. * * @param phoneNumber * the number to be formatted. * @param defaultCountryIso * the ISO 3166-1 two letters country code whose convention will * be used if the given number doesn't have the country code. * @return the formatted number, or null if the given number is not valid. * */ @SuppressLint("Override") public static String formatNumber(String phoneNumber, String defaultCountryIso) { // Do not attempt to format numbers that start with a hash or star symbol. if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { return phoneNumber; } PhoneNumberUtil util = PhoneNumberUtil.getInstance(); String result = null; try { Phonenumber.PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); result = util.formatInOriginalFormat(pn, defaultCountryIso); } catch (NumberParseException e) { } return result; } /** * Format the given phoneNumber to the E.164 representation. * <p> * The given phone number must have an area code and could have a country * code. * <p> * The defaultCountryIso is used to validate the given number and generate * the E.164 phone number if the given number doesn't have a country code. * * @param phoneNumber * the phone number to format * @param defaultCountryIso * the ISO 3166-1 two letters country code * @return the E.164 representation, or null if the given phone number is * not valid. * */ @SuppressLint("Override") public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) { PhoneNumberUtil util = PhoneNumberUtil.getInstance(); String result = null; try { Phonenumber.PhoneNumber pn = util.parse(phoneNumber, defaultCountryIso); if (util.isValidNumber(pn)) { result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.E164); } } catch (NumberParseException e) { } return result; } /** * Normalize a phone number by removing the characters other than digits. If * the given number has keypad letters, the letters will be converted to * digits first. * * @param phoneNumber * the number to be normalized. * @return the normalized number. * */ @SuppressLint("Override") public static String normalizeNumber(String phoneNumber) { StringBuilder sb = new StringBuilder(); int len = phoneNumber.length(); for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) int digit = Character.digit(c, 10); if (digit != -1) { sb.append(digit); } else if (i == 0 && c == '+') { sb.append(c); } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber)); } } return sb.toString(); } /** or -1 if both are negative */ static private int minPositive (int a, int b) { if (a >= 0 && b >= 0) { return (a < b) ? a : b; } else if (a >= 0) { /* && b < 0 */ return a; } else if (b >= 0) { /* && a < 0 */ return b; } else { /* a < 0 && b < 0 */ return -1; } } /** index of the last character of the network portion * (eg anything after is a post-dial string) */ static private int indexOfLastNetworkChar(String a) { int pIndex, wIndex; int origLength; int trimIndex; origLength = a.length(); pIndex = a.indexOf(PAUSE); wIndex = a.indexOf(WAIT); trimIndex = minPositive(pIndex, wIndex); if (trimIndex < 0) { return origLength - 1; } else { return trimIndex - 1; } } /** * Return true iff the network portion of <code>address</code> is, * as far as we can tell on the device, suitable for use as an SMS * destination address. */ public static boolean isReallyWellFormedSmsAddress(String address) { String networkPortion = PhoneNumberUtils.extractNetworkPortion(address); return (!(networkPortion.equals("+") || TextUtils.isEmpty(networkPortion))) && isReallyDialable(networkPortion); } private static boolean isReallyDialable(String address) { for (int i = 0, count = address.length(); i < count; i++) { if (!isReallyDialable(address.charAt(i))) { return false; } } return true; } /** * Compare phone numbers a and b, return true if they're identical * enough for caller ID purposes. * * - Compares from right to left * - requires MIN_MATCH (7) characters to match * - handles common trunk prefixes and international prefixes * (basically, everything except the Russian trunk prefix) * * Note that this method does not return false even when the two phone numbers * are not exactly same; rather; we can call this method "similar()", not "equals()". * * @hide */ public static boolean compareLoosely(String a, String b) { int ia, ib; int matched; int numNonDialableCharsInA = 0; int numNonDialableCharsInB = 0; if (a == null || b == null) return a == b; if (a.length() == 0 || b.length() == 0) { return false; } ia = indexOfLastNetworkChar (a); ib = indexOfLastNetworkChar (b); matched = 0; while (ia >= 0 && ib >=0) { char ca, cb; boolean skipCmp = false; ca = a.charAt(ia); if (!isDialable(ca)) { ia--; skipCmp = true; numNonDialableCharsInA++; } cb = b.charAt(ib); if (!isDialable(cb)) { ib--; skipCmp = true; numNonDialableCharsInB++; } if (!skipCmp) { if (cb != ca && ca != WILD && cb != WILD) { break; } ia--; ib--; matched++; } } if (matched < MIN_MATCH) { int effectiveALen = a.length() - numNonDialableCharsInA; int effectiveBLen = b.length() - numNonDialableCharsInB; // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH, // treat them as equal (i.e. 404-04 and 40404) return effectiveALen == effectiveBLen && effectiveALen == matched; } // At least one string has matched completely; if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { return true; } /* * Now, what remains must be one of the following for a * match: * * - a '+' on one and a '00' or a '011' on the other * - a '0' on one and a (+,00)<country code> on the other * (for this, a '0' and a '00' prefix would have succeeded above) */ if (matchIntlPrefix(a, ia + 1) && matchIntlPrefix (b, ib +1) ) { return true; } if (matchTrunkPrefix(a, ia + 1) && matchIntlPrefixAndCC(b, ib +1) ) { return true; } return matchTrunkPrefix(b, ib + 1) && matchIntlPrefixAndCC(a, ia + 1); } /** all of 'a' up to len must be a (+|00|011)country code) * We're fast and loose with the country code. Any \d{1,3} matches */ private static boolean matchIntlPrefixAndCC(String a, int len) { /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ /* 0 1 2 3 45 6 7 8 */ int state = 0; for (int i = 0 ; i < len ; i++ ) { char c = a.charAt(i); switch (state) { case 0: if (c == '+') state = 1; else if (c == '0') state = 2; else if (isNonSeparator(c)) return false; break; case 2: if (c == '0') state = 3; else if (c == '1') state = 4; else if (isNonSeparator(c)) return false; break; case 4: if (c == '1') state = 5; else if (isNonSeparator(c)) return false; break; case 1: case 3: case 5: if (isISODigit(c)) state = 6; else if (isNonSeparator(c)) return false; break; case 6: case 7: if (isISODigit(c)) state++; else if (isNonSeparator(c)) return false; break; default: if (isNonSeparator(c)) return false; } } return state == 6 || state == 7 || state == 8; } /** * Phone numbers are stored in "lookup" form in the database * as reversed strings to allow for caller ID lookup * * This method takes a phone number and makes a valid SQL "LIKE" * string that will match the lookup form * */ /** all of a up to len must be an international prefix or * separators/non-dialing digits */ private static boolean matchIntlPrefix(String a, int len) { /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ /* 0 1 2 3 45 */ int state = 0; for (int i = 0 ; i < len ; i++) { char c = a.charAt(i); switch (state) { case 0: if (c == '+') state = 1; else if (c == '0') state = 2; else if (isNonSeparator(c)) return false; break; case 2: if (c == '0') state = 3; else if (c == '1') state = 4; else if (isNonSeparator(c)) return false; break; case 4: if (c == '1') state = 5; else if (isNonSeparator(c)) return false; break; default: if (isNonSeparator(c)) return false; break; } } return state == 1 || state == 3 || state == 5; } /** all of 'a' up to len must match non-US trunk prefix ('0') */ private static boolean matchTrunkPrefix(String a, int len) { boolean found; found = false; for (int i = 0 ; i < len ; i++) { char c = a.charAt(i); if (c == '0' && !found) { found = true; } else if (isNonSeparator(c)) { return false; } } return found; } }