Home | History | Annotate | Download | only in phonenumberutil
      1 /*
      2  * Copyright (C) 2013 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.dialer.phonenumberutil;
     18 
     19 import android.content.Context;
     20 import android.provider.CallLog;
     21 import android.support.annotation.Nullable;
     22 import android.telecom.PhoneAccountHandle;
     23 import android.telephony.PhoneNumberUtils;
     24 import android.telephony.TelephonyManager;
     25 import android.text.TextUtils;
     26 import com.android.dialer.common.LogUtil;
     27 import com.android.dialer.telecom.TelecomUtil;
     28 import com.google.i18n.phonenumbers.NumberParseException;
     29 import com.google.i18n.phonenumbers.PhoneNumberUtil;
     30 import com.google.i18n.phonenumbers.Phonenumber;
     31 import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
     32 import java.util.Arrays;
     33 import java.util.HashSet;
     34 import java.util.Locale;
     35 import java.util.Set;
     36 
     37 public class PhoneNumberHelper {
     38 
     39   private static final String TAG = "PhoneNumberUtil";
     40   private static final Set<String> LEGACY_UNKNOWN_NUMBERS =
     41       new HashSet<>(Arrays.asList("-1", "-2", "-3"));
     42 
     43   /** Returns true if it is possible to place a call to the given number. */
     44   public static boolean canPlaceCallsTo(CharSequence number, int presentation) {
     45     return presentation == CallLog.Calls.PRESENTATION_ALLOWED
     46         && !TextUtils.isEmpty(number)
     47         && !isLegacyUnknownNumbers(number);
     48   }
     49 
     50   /**
     51    * Returns true if the given number is the number of the configured voicemail. To be able to
     52    * mock-out this, it is not a static method.
     53    */
     54   public static boolean isVoicemailNumber(
     55       Context context, PhoneAccountHandle accountHandle, CharSequence number) {
     56     if (TextUtils.isEmpty(number)) {
     57       return false;
     58     }
     59     return TelecomUtil.isVoicemailNumber(context, accountHandle, number.toString());
     60   }
     61 
     62   /**
     63    * Returns true if the given number is a SIP address. To be able to mock-out this, it is not a
     64    * static method.
     65    */
     66   public static boolean isSipNumber(CharSequence number) {
     67     return number != null && isUriNumber(number.toString());
     68   }
     69 
     70   public static boolean isUnknownNumberThatCanBeLookedUp(
     71       Context context, PhoneAccountHandle accountHandle, CharSequence number, int presentation) {
     72     if (presentation == CallLog.Calls.PRESENTATION_UNKNOWN) {
     73       return false;
     74     }
     75     if (presentation == CallLog.Calls.PRESENTATION_RESTRICTED) {
     76       return false;
     77     }
     78     if (presentation == CallLog.Calls.PRESENTATION_PAYPHONE) {
     79       return false;
     80     }
     81     if (TextUtils.isEmpty(number)) {
     82       return false;
     83     }
     84     if (isVoicemailNumber(context, accountHandle, number)) {
     85       return false;
     86     }
     87     if (isLegacyUnknownNumbers(number)) {
     88       return false;
     89     }
     90     return true;
     91   }
     92 
     93   public static boolean isLegacyUnknownNumbers(CharSequence number) {
     94     return number != null && LEGACY_UNKNOWN_NUMBERS.contains(number.toString());
     95   }
     96 
     97   /**
     98    * @return a geographical description string for the specified number.
     99    * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
    100    */
    101   public static String getGeoDescription(Context context, String number) {
    102     LogUtil.v("PhoneNumberHelper.getGeoDescription", "" + LogUtil.sanitizePii(number));
    103 
    104     if (TextUtils.isEmpty(number)) {
    105       return null;
    106     }
    107 
    108     PhoneNumberUtil util = PhoneNumberUtil.getInstance();
    109     PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
    110 
    111     Locale locale = context.getResources().getConfiguration().locale;
    112     String countryIso = getCurrentCountryIso(context, locale);
    113     Phonenumber.PhoneNumber pn = null;
    114     try {
    115       LogUtil.v(
    116           "PhoneNumberHelper.getGeoDescription",
    117           "parsing '" + LogUtil.sanitizePii(number) + "' for countryIso '" + countryIso + "'...");
    118       pn = util.parse(number, countryIso);
    119       LogUtil.v(
    120           "PhoneNumberHelper.getGeoDescription", "- parsed number: " + LogUtil.sanitizePii(pn));
    121     } catch (NumberParseException e) {
    122       LogUtil.e(
    123           "PhoneNumberHelper.getGeoDescription",
    124           "getGeoDescription: NumberParseException for incoming number '"
    125               + LogUtil.sanitizePii(number)
    126               + "'");
    127     }
    128 
    129     if (pn != null) {
    130       String description = geocoder.getDescriptionForNumber(pn, locale);
    131       LogUtil.v("PhoneNumberHelper.getGeoDescription", "- got description: '" + description + "'");
    132       return description;
    133     }
    134 
    135     return null;
    136   }
    137 
    138   /**
    139    * @return The ISO 3166-1 two letters country code of the country the user is in based on the
    140    *     network location. If the network location does not exist, fall back to the locale setting.
    141    */
    142   public static String getCurrentCountryIso(Context context, Locale locale) {
    143     // Without framework function calls, this seems to be the most accurate location service
    144     // we can rely on.
    145     final TelephonyManager telephonyManager =
    146         (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    147 
    148     String countryIso = telephonyManager.getNetworkCountryIso();
    149     if (TextUtils.isEmpty(countryIso)) {
    150       countryIso = locale.getCountry();
    151       LogUtil.i(
    152           "PhoneNumberHelper.getCurrentCountryIso",
    153           "No CountryDetector; falling back to countryIso based on locale: " + countryIso);
    154     }
    155     countryIso = countryIso.toUpperCase();
    156 
    157     return countryIso;
    158   }
    159 
    160   /**
    161    * @return Formatted phone number. e.g. 1-123-456-7890. Returns the original number if formatting
    162    *     failed.
    163    */
    164   public static String formatNumber(@Nullable String number, Context context) {
    165     // The number can be null e.g. schema is voicemail and uri content is empty.
    166     if (number == null) {
    167       return null;
    168     }
    169     String formattedNumber =
    170         PhoneNumberUtils.formatNumber(
    171             number, PhoneNumberHelper.getCurrentCountryIso(context, Locale.getDefault()));
    172     return formattedNumber != null ? formattedNumber : number;
    173   }
    174 
    175   /**
    176    * Determines if the specified number is actually a URI (i.e. a SIP address) rather than a regular
    177    * PSTN phone number, based on whether or not the number contains an "@" character.
    178    *
    179    * @param number Phone number
    180    * @return true if number contains @
    181    *     <p>TODO: Remove if PhoneNumberUtils.isUriNumber(String number) is made public.
    182    */
    183   public static boolean isUriNumber(String number) {
    184     // Note we allow either "@" or "%40" to indicate a URI, in case
    185     // the passed-in string is URI-escaped.  (Neither "@" nor "%40"
    186     // will ever be found in a legal PSTN number.)
    187     return number != null && (number.contains("@") || number.contains("%40"));
    188   }
    189 
    190   /**
    191    * @param number SIP address of the form "username@domainname" (or the URI-escaped equivalent
    192    *     "username%40domainname")
    193    *     <p>TODO: Remove if PhoneNumberUtils.getUsernameFromUriNumber(String number) is made public.
    194    * @return the "username" part of the specified SIP address, i.e. the part before the "@"
    195    *     character (or "%40").
    196    */
    197   public static String getUsernameFromUriNumber(String number) {
    198     // The delimiter between username and domain name can be
    199     // either "@" or "%40" (the URI-escaped equivalent.)
    200     int delimiterIndex = number.indexOf('@');
    201     if (delimiterIndex < 0) {
    202       delimiterIndex = number.indexOf("%40");
    203     }
    204     if (delimiterIndex < 0) {
    205       LogUtil.i(
    206           "PhoneNumberHelper.getUsernameFromUriNumber",
    207           "getUsernameFromUriNumber: no delimiter found in SIP address: "
    208               + LogUtil.sanitizePii(number));
    209       return number;
    210     }
    211     return number.substring(0, delimiterIndex);
    212   }
    213 
    214 
    215   private static boolean isVerizon(Context context) {
    216     // Verizon MCC/MNC codes copied from com/android/voicemailomtp/res/xml/vvm_config.xml.
    217     // TODO: Need a better way to do per carrier and per OEM configurations.
    218     switch (context.getSystemService(TelephonyManager.class).getSimOperator()) {
    219       case "310004":
    220       case "310010":
    221       case "310012":
    222       case "310013":
    223       case "310590":
    224       case "310890":
    225       case "310910":
    226       case "311110":
    227       case "311270":
    228       case "311271":
    229       case "311272":
    230       case "311273":
    231       case "311274":
    232       case "311275":
    233       case "311276":
    234       case "311277":
    235       case "311278":
    236       case "311279":
    237       case "311280":
    238       case "311281":
    239       case "311282":
    240       case "311283":
    241       case "311284":
    242       case "311285":
    243       case "311286":
    244       case "311287":
    245       case "311288":
    246       case "311289":
    247       case "311390":
    248       case "311480":
    249       case "311481":
    250       case "311482":
    251       case "311483":
    252       case "311484":
    253       case "311485":
    254       case "311486":
    255       case "311487":
    256       case "311488":
    257       case "311489":
    258         return true;
    259       default:
    260         return false;
    261     }
    262   }
    263 
    264   /**
    265    * Gets the label to display for a phone call where the presentation is set as
    266    * PRESENTATION_RESTRICTED. For Verizon we want this to be displayed as "Restricted". For all
    267    * other carriers we want this to be be displayed as "Private number".
    268    */
    269   public static CharSequence getDisplayNameForRestrictedNumber(Context context) {
    270     if (isVerizon(context)) {
    271       return context.getString(R.string.private_num_verizon);
    272     } else {
    273       return context.getString(R.string.private_num_non_verizon);
    274     }
    275   }
    276 }
    277