Home | History | Annotate | Download | only in compat
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.compat;
     18 
     19 import android.telephony.PhoneNumberUtils;
     20 import android.text.Spannable;
     21 import android.text.TextUtils;
     22 import android.text.style.TtsSpan;
     23 
     24 import com.google.i18n.phonenumbers.NumberParseException;
     25 import com.google.i18n.phonenumbers.PhoneNumberUtil;
     26 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
     27 
     28 /**
     29  * This class contains static utility methods extracted from PhoneNumberUtils, and the
     30  * methods were added in API level 23. In this way, we could enable the corresponding functionality
     31  * for pre-M devices. We need maintain this class and keep it synced with PhoneNumberUtils.
     32  * Another thing to keep in mind is that we use com.google.i18n rather than com.android.i18n in
     33  * here, so we need make sure the application behavior is preserved.
     34  */
     35 public class PhoneNumberUtilsCompat {
     36     /**
     37      * Not instantiable.
     38      */
     39     private PhoneNumberUtilsCompat() {}
     40 
     41     public static String normalizeNumber(String phoneNumber) {
     42         if (CompatUtils.isLollipopCompatible()) {
     43             return PhoneNumberUtils.normalizeNumber(phoneNumber);
     44         } else {
     45             return normalizeNumberInternal(phoneNumber);
     46         }
     47     }
     48 
     49     /**
     50      * Implementation copied from {@link PhoneNumberUtils#normalizeNumber}
     51      */
     52     private static String normalizeNumberInternal(String phoneNumber) {
     53         if (TextUtils.isEmpty(phoneNumber)) {
     54             return "";
     55         }
     56         StringBuilder sb = new StringBuilder();
     57         int len = phoneNumber.length();
     58         for (int i = 0; i < len; i++) {
     59             char c = phoneNumber.charAt(i);
     60             // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
     61             int digit = Character.digit(c, 10);
     62             if (digit != -1) {
     63                 sb.append(digit);
     64             } else if (sb.length() == 0 && c == '+') {
     65                 sb.append(c);
     66             } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
     67                 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
     68             }
     69         }
     70         return sb.toString();
     71     }
     72 
     73     public static String formatNumber(
     74             String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
     75         if (CompatUtils.isLollipopCompatible()) {
     76             return PhoneNumberUtils.formatNumber(phoneNumber, phoneNumberE164, defaultCountryIso);
     77         } else {
     78             // This method was deprecated in API level 21, so it's only used on pre-L SDKs.
     79             return PhoneNumberUtils.formatNumber(phoneNumber);
     80         }
     81     }
     82 
     83     public static CharSequence createTtsSpannable(CharSequence phoneNumber) {
     84         if (CompatUtils.isMarshmallowCompatible()) {
     85             return PhoneNumberUtils.createTtsSpannable(phoneNumber);
     86         } else {
     87             return createTtsSpannableInternal(phoneNumber);
     88         }
     89     }
     90 
     91     public static TtsSpan createTtsSpan(String phoneNumber) {
     92         if (CompatUtils.isMarshmallowCompatible()) {
     93             return PhoneNumberUtils.createTtsSpan(phoneNumber);
     94         } else if (CompatUtils.isLollipopCompatible()) {
     95             return createTtsSpanLollipop(phoneNumber);
     96         } else {
     97             return null;
     98         }
     99     }
    100 
    101     /**
    102      * Copied from {@link PhoneNumberUtils#createTtsSpannable}
    103      */
    104     private static CharSequence createTtsSpannableInternal(CharSequence phoneNumber) {
    105         if (phoneNumber == null) {
    106             return null;
    107         }
    108         Spannable spannable = Spannable.Factory.getInstance().newSpannable(phoneNumber);
    109         addTtsSpanInternal(spannable, 0, spannable.length());
    110         return spannable;
    111     }
    112 
    113     /**
    114      * Compat method for addTtsSpan, see {@link PhoneNumberUtils#addTtsSpan}
    115      */
    116     public static void addTtsSpan(Spannable s, int start, int endExclusive) {
    117         if (CompatUtils.isMarshmallowCompatible()) {
    118             PhoneNumberUtils.addTtsSpan(s, start, endExclusive);
    119         } else {
    120             addTtsSpanInternal(s, start, endExclusive);
    121         }
    122     }
    123 
    124     /**
    125      * Copied from {@link PhoneNumberUtils#addTtsSpan}
    126      */
    127     private static void addTtsSpanInternal(Spannable s, int start, int endExclusive) {
    128         s.setSpan(createTtsSpan(s.subSequence(start, endExclusive).toString()),
    129                 start,
    130                 endExclusive,
    131                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    132     }
    133 
    134     /**
    135      * Copied from {@link PhoneNumberUtils#createTtsSpan}
    136      */
    137     private static TtsSpan createTtsSpanLollipop(String phoneNumberString) {
    138         if (phoneNumberString == null) {
    139             return null;
    140         }
    141 
    142         // Parse the phone number
    143         final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
    144         PhoneNumber phoneNumber = null;
    145         try {
    146             // Don't supply a defaultRegion so this fails for non-international numbers because
    147             // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already
    148             // present
    149             phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null);
    150         } catch (NumberParseException ignored) {
    151         }
    152 
    153         // Build a telephone tts span
    154         final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder();
    155         if (phoneNumber == null) {
    156             // Strip separators otherwise TalkBack will be silent
    157             // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel)
    158             builder.setNumberParts(splitAtNonNumerics(phoneNumberString));
    159         } else {
    160             if (phoneNumber.hasCountryCode()) {
    161                 builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode()));
    162             }
    163             builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber()));
    164         }
    165         return builder.build();
    166     }
    167 
    168 
    169 
    170     /**
    171      * Split a phone number using spaces, ignoring anything that is not a digit
    172      * @param number A {@code CharSequence} before splitting, e.g., "+20(123)-456#"
    173      * @return A {@code String} after splitting, e.g., "20 123 456".
    174      */
    175     private static String splitAtNonNumerics(CharSequence number) {
    176         StringBuilder sb = new StringBuilder(number.length());
    177         for (int i = 0; i < number.length(); i++) {
    178             sb.append(PhoneNumberUtils.isISODigit(number.charAt(i))
    179                     ? number.charAt(i)
    180                     : " ");
    181         }
    182         // It is very important to remove extra spaces. At time of writing, any leading or trailing
    183         // spaces, or any sequence of more than one space, will confuse TalkBack and cause the TTS
    184         // span to be non-functional!
    185         return sb.toString().replaceAll(" +", " ").trim();
    186     }
    187 
    188 }
    189