1 /* 2 * Copyright (C) 2016 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 package com.android.providers.blockednumber; 17 18 import static android.util.Log.isLoggable; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.location.Country; 24 import android.location.CountryDetector; 25 import android.net.Uri; 26 import android.os.Build; 27 import android.telecom.PhoneAccount; 28 import android.telephony.PhoneNumberUtils; 29 import android.text.TextUtils; 30 31 import java.util.Locale; 32 33 public class Utils { 34 /** 35 * When generating a bug report, include the last X dialable digits when logging phone numbers. 36 */ 37 private static final int NUM_DIALABLE_DIGITS_TO_LOG = Build.IS_USER ? 0 : 2; 38 Utils()39 private Utils() { 40 } 41 42 public static final int MIN_INDEX_LEN = 8; 43 public static String TAG = "BlockedNumberProvider"; 44 public static boolean VERBOSE = isLoggable(TAG, android.util.Log.VERBOSE); 45 46 /** 47 * @return The current country code. 48 */ getCurrentCountryIso(@onNull Context context)49 public static @NonNull String getCurrentCountryIso(@NonNull Context context) { 50 final CountryDetector detector = (CountryDetector) context.getSystemService( 51 Context.COUNTRY_DETECTOR); 52 if (detector != null) { 53 final Country country = detector.detectCountry(); 54 if (country != null) { 55 return country.getCountryIso(); 56 } 57 } 58 final Locale locale = context.getResources().getConfiguration().locale; 59 return locale.getCountry(); 60 } 61 62 /** 63 * Converts a phone number to an E164 number, assuming the current country. If {@code 64 * incomingE16Number} is provided, it'll just strip it and returns. If the number is not valid, 65 * it'll return "". 66 * 67 * <p>Special case: if {@code rawNumber} contains '@', it's considered as an email address and 68 * returned unmodified. 69 */ getE164Number(@onNull Context context, @Nullable String rawNumber, @Nullable String incomingE16Number)70 public static @NonNull String getE164Number(@NonNull Context context, 71 @Nullable String rawNumber, @Nullable String incomingE16Number) { 72 if (rawNumber != null && rawNumber.contains("@")) { 73 return rawNumber; 74 } 75 if (!TextUtils.isEmpty(incomingE16Number)) { 76 return incomingE16Number; 77 } 78 if (TextUtils.isEmpty(rawNumber)) { 79 return ""; 80 } 81 final String e164 = 82 PhoneNumberUtils.formatNumberToE164(rawNumber, getCurrentCountryIso(context)); 83 return e164 == null ? "" : e164; 84 } 85 wrapSelectionWithParens(@ullable String selection)86 public static @Nullable String wrapSelectionWithParens(@Nullable String selection) { 87 return TextUtils.isEmpty(selection) ? null : "(" + selection + ")"; 88 } 89 90 /** 91 * Generates an obfuscated string for a calling handle in {@link Uri} format, or a raw phone 92 * phone number in {@link String} format. 93 * @param pii The information to obfuscate. 94 * @return The obfuscated string. 95 */ piiHandle(Object pii)96 public static String piiHandle(Object pii) { 97 if (pii == null || VERBOSE) { 98 return String.valueOf(pii); 99 } 100 101 StringBuilder sb = new StringBuilder(); 102 if (pii instanceof Uri) { 103 Uri uri = (Uri) pii; 104 String scheme = uri.getScheme(); 105 106 if (!TextUtils.isEmpty(scheme)) { 107 sb.append(scheme).append(":"); 108 } 109 110 String textToObfuscate = uri.getSchemeSpecificPart(); 111 if (PhoneAccount.SCHEME_TEL.equals(scheme)) { 112 obfuscatePhoneNumber(sb, textToObfuscate); 113 } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 114 for (int i = 0; i < textToObfuscate.length(); i++) { 115 char c = textToObfuscate.charAt(i); 116 if (c != '@' && c != '.') { 117 c = '*'; 118 } 119 sb.append(c); 120 } 121 } else { 122 sb.append(pii(pii)); 123 } 124 } else if (pii instanceof String) { 125 String number = (String) pii; 126 obfuscatePhoneNumber(sb, number); 127 } 128 129 return sb.toString(); 130 } 131 132 /** 133 * Obfuscates a phone number, allowing NUM_DIALABLE_DIGITS_TO_LOG digits to be exposed for the 134 * phone number. 135 * @param sb String buffer to write obfuscated number to. 136 * @param phoneNumber The number to obfuscate. 137 */ obfuscatePhoneNumber(StringBuilder sb, String phoneNumber)138 private static void obfuscatePhoneNumber(StringBuilder sb, String phoneNumber) { 139 int numDigitsToObfuscate = getDialableCount(phoneNumber) 140 - NUM_DIALABLE_DIGITS_TO_LOG; 141 for (int i = 0; i < phoneNumber.length(); i++) { 142 char c = phoneNumber.charAt(i); 143 boolean isDialable = PhoneNumberUtils.isDialable(c); 144 if (isDialable) { 145 numDigitsToObfuscate--; 146 } 147 sb.append(isDialable && numDigitsToObfuscate >= 0 ? "*" : c); 148 } 149 } 150 151 /** 152 * Redact personally identifiable information for production users. 153 * If we are running in verbose mode, return the original string, 154 * and return "***" otherwise. 155 */ pii(Object pii)156 public static String pii(Object pii) { 157 if (pii == null || VERBOSE) { 158 return String.valueOf(pii); 159 } 160 return "***"; 161 } 162 163 /** 164 * Determines the number of dialable characters in a string. 165 * @param toCount The string to count dialable characters in. 166 * @return The count of dialable characters. 167 */ getDialableCount(String toCount)168 private static int getDialableCount(String toCount) { 169 int numDialable = 0; 170 for (char c : toCount.toCharArray()) { 171 if (PhoneNumberUtils.isDialable(c)) { 172 numDialable++; 173 } 174 } 175 return numDialable; 176 } 177 } 178