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. */ canPlaceCallsTo(CharSequence number, int presentation)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 */ isVoicemailNumber( Context context, PhoneAccountHandle accountHandle, CharSequence number)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 */ isSipNumber(CharSequence number)66 public static boolean isSipNumber(CharSequence number) { 67 return number != null && isUriNumber(number.toString()); 68 } 69 isUnknownNumberThatCanBeLookedUp( Context context, PhoneAccountHandle accountHandle, CharSequence number, int presentation)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 isLegacyUnknownNumbers(CharSequence number)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 */ getGeoDescription(Context context, String number)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 */ getCurrentCountryIso(Context context, Locale locale)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 */ formatNumber(@ullable String number, Context context)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 */ isUriNumber(String number)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 */ getUsernameFromUriNumber(String number)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 isVerizon(Context context)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 */ getDisplayNameForRestrictedNumber(Context context)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