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