blob: 38043eda8cda87ada71027700323aaf10bbcc24e [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
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
17package com.android.incallui;
18
Eric Erfanianccca3152017-02-22 16:32:36 -080019import android.content.Context;
20import android.content.Loader;
21import android.content.Loader.OnLoadCompleteListener;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.net.Uri;
Eric Erfanianccca3152017-02-22 16:32:36 -080023import android.telecom.TelecomManager;
24import android.text.TextUtils;
25import com.android.contacts.common.model.Contact;
26import com.android.contacts.common.model.ContactLoader;
27import com.android.dialer.common.LogUtil;
28import com.android.dialer.phonenumbercache.CachedNumberLookupService;
29import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
30import com.android.dialer.phonenumbercache.ContactInfo;
31import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanianccca3152017-02-22 16:32:36 -080032import com.android.dialer.util.PermissionsUtil;
33import com.android.incallui.call.DialerCall;
34import java.util.Arrays;
35
36/** Utility methods for contact and caller info related functionality */
37public class CallerInfoUtils {
38
39 private static final String TAG = CallerInfoUtils.class.getSimpleName();
40
41 private static final int QUERY_TOKEN = -1;
42
43 public CallerInfoUtils() {}
44
45 /**
46 * This is called to get caller info for a call. This will return a CallerInfo object immediately
47 * based off information in the call, but more information is returned to the
48 * OnQueryCompleteListener (which contains information about the phone number label, user's name,
49 * etc).
50 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -070051 static CallerInfo getCallerInfoForCall(
Eric Erfanianccca3152017-02-22 16:32:36 -080052 Context context,
53 DialerCall call,
54 Object cookie,
55 CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
56 CallerInfo info = buildCallerInfo(context, call);
57
58 // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.
59
60 if (info.numberPresentation == TelecomManager.PRESENTATION_ALLOWED) {
Eric Erfanianc857f902017-05-15 14:05:33 -070061 if (PermissionsUtil.hasContactsReadPermissions(context)) {
Eric Erfanianccca3152017-02-22 16:32:36 -080062 // Start the query with the number provided from the call.
63 LogUtil.d(
64 "CallerInfoUtils.getCallerInfoForCall",
65 "Actually starting CallerInfoAsyncQuery.startQuery()...");
66
wangqi1420a222017-09-21 09:37:40 -070067 // noinspection MissingPermission
Eric Erfanianccca3152017-02-22 16:32:36 -080068 CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, info, listener, cookie);
69 } else {
70 LogUtil.w(
71 "CallerInfoUtils.getCallerInfoForCall",
72 "Dialer doesn't have permission to read contacts."
73 + " Not calling CallerInfoAsyncQuery.startQuery().");
74 }
75 }
76 return info;
77 }
78
Eric Erfaniand5e47f62017-03-15 14:41:07 -070079 static CallerInfo buildCallerInfo(Context context, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -080080 CallerInfo info = new CallerInfo();
81
82 // Store CNAP information retrieved from the Connection (we want to do this
83 // here regardless of whether the number is empty or not).
84 info.cnapName = call.getCnapName();
85 info.name = info.cnapName;
86 info.numberPresentation = call.getNumberPresentation();
87 info.namePresentation = call.getCnapNamePresentation();
88 info.callSubject = call.getCallSubject();
Eric Erfaniand5e47f62017-03-15 14:41:07 -070089 info.contactExists = false;
wangqi1420a222017-09-21 09:37:40 -070090 info.countryIso = PhoneNumberHelper.getCurrentCountryIso(context, call.getAccountHandle());
Eric Erfanianccca3152017-02-22 16:32:36 -080091
92 String number = call.getNumber();
93 if (!TextUtils.isEmpty(number)) {
94 // Don't split it if it's a SIP number.
95 if (!PhoneNumberHelper.isUriNumber(number)) {
96 final String[] numbers = number.split("&");
97 number = numbers[0];
98 if (numbers.length > 1) {
99 info.forwardingNumber = numbers[1];
100 }
101 number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation);
102 }
103 info.phoneNumber = number;
104 }
105
106 // Because the InCallUI is immediately launched before the call is connected, occasionally
107 // a voicemail call will be passed to InCallUI as a "voicemail:" URI without a number.
108 // This call should still be handled as a voicemail call.
wangqi9982f0d2017-10-11 17:46:07 -0700109 if (call.isVoiceMailNumber()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800110 info.markAsVoiceMail(context);
111 }
112
113 ContactInfoCache.getInstance(context).maybeInsertCnapInformationIntoCache(context, call, info);
114
115 return info;
116 }
117
118 /**
119 * Creates a new {@link CachedContactInfo} from a {@link CallerInfo}
120 *
121 * @param lookupService the {@link CachedNumberLookupService} used to build a new {@link
122 * CachedContactInfo}
123 * @param {@link CallerInfo} object
124 * @return a CachedContactInfo object created from this CallerInfo
125 * @throws NullPointerException if lookupService or ci are null
126 */
127 public static CachedContactInfo buildCachedContactInfo(
128 CachedNumberLookupService lookupService, CallerInfo ci) {
129 ContactInfo info = new ContactInfo();
130 info.name = ci.name;
131 info.type = ci.numberType;
132 info.label = ci.phoneLabel;
133 info.number = ci.phoneNumber;
134 info.normalizedNumber = ci.normalizedNumber;
135 info.photoUri = ci.contactDisplayPhotoUri;
136 info.userType = ci.userType;
137
138 CachedContactInfo cacheInfo = lookupService.buildCachedContactInfo(info);
139 cacheInfo.setLookupKey(ci.lookupKeyOrNull);
140 return cacheInfo;
141 }
142
Eric Erfanianccca3152017-02-22 16:32:36 -0800143 /**
144 * Handles certain "corner cases" for CNAP. When we receive weird phone numbers from the network
145 * to indicate different number presentations, convert them to expected number and presentation
146 * values within the CallerInfo object.
147 *
148 * @param number number we use to verify if we are in a corner case
149 * @param presentation presentation value used to verify if we are in a corner case
150 * @return the new String that should be used for the phone number
151 */
152 /* package */
153 static String modifyForSpecialCnapCases(
154 Context context, CallerInfo ci, String number, int presentation) {
155 // Obviously we return number if ci == null, but still return number if
156 // number == null, because in these cases the correct string will still be
157 // displayed/logged after this function returns based on the presentation value.
158 if (ci == null || number == null) {
159 return number;
160 }
161
162 LogUtil.d(
163 "CallerInfoUtils.modifyForSpecialCnapCases",
164 "modifyForSpecialCnapCases: initially, number="
165 + toLogSafePhoneNumber(number)
166 + ", presentation="
167 + presentation
168 + " ci "
169 + ci);
170
171 // "ABSENT NUMBER" is a possible value we could get from the network as the
172 // phone number, so if this happens, change it to "Unknown" in the CallerInfo
173 // and fix the presentation to be the same.
174 final String[] absentNumberValues = context.getResources().getStringArray(R.array.absent_num);
175 if (Arrays.asList(absentNumberValues).contains(number)
176 && presentation == TelecomManager.PRESENTATION_ALLOWED) {
177 number = context.getString(R.string.unknown);
178 ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
179 }
180
181 // Check for other special "corner cases" for CNAP and fix them similarly. Corner
182 // cases only apply if we received an allowed presentation from the network, so check
183 // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
184 // match the presentation passed in for verification (meaning we changed it previously
185 // because it's a corner case and we're being called from a different entry point).
186 if (ci.numberPresentation == TelecomManager.PRESENTATION_ALLOWED
187 || (ci.numberPresentation != presentation
188 && presentation == TelecomManager.PRESENTATION_ALLOWED)) {
189 // For all special strings, change number & numberPrentation.
190 if (isCnapSpecialCaseRestricted(number)) {
191 number = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context).toString();
192 ci.numberPresentation = TelecomManager.PRESENTATION_RESTRICTED;
193 } else if (isCnapSpecialCaseUnknown(number)) {
194 number = context.getString(R.string.unknown);
195 ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
196 }
197 LogUtil.d(
198 "CallerInfoUtils.modifyForSpecialCnapCases",
199 "SpecialCnap: number="
200 + toLogSafePhoneNumber(number)
201 + "; presentation now="
202 + ci.numberPresentation);
203 }
204 LogUtil.d(
205 "CallerInfoUtils.modifyForSpecialCnapCases",
206 "returning number string=" + toLogSafePhoneNumber(number));
207 return number;
208 }
209
210 private static boolean isCnapSpecialCaseRestricted(String n) {
211 return n.equals("PRIVATE") || n.equals("P") || n.equals("RES") || n.equals("PRIVATENUMBER");
212 }
213
214 private static boolean isCnapSpecialCaseUnknown(String n) {
215 return n.equals("UNAVAILABLE") || n.equals("UNKNOWN") || n.equals("UNA") || n.equals("U");
216 }
217
218 /* package */
219 static String toLogSafePhoneNumber(String number) {
220 // For unknown number, log empty string.
221 if (number == null) {
222 return "";
223 }
224
225 // Todo: Figure out an equivalent for VDBG
226 if (false) {
227 // When VDBG is true we emit PII.
228 return number;
229 }
230
231 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
232 // sanitized phone numbers.
233 StringBuilder builder = new StringBuilder();
234 for (int i = 0; i < number.length(); i++) {
235 char c = number.charAt(i);
236 if (c == '-' || c == '@' || c == '.' || c == '&') {
237 builder.append(c);
238 } else {
239 builder.append('x');
240 }
241 }
242 return builder.toString();
243 }
244
245 /**
246 * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are
247 * viewing a particular contact, so that it can download the high-res photo.
248 */
249 public static void sendViewNotification(Context context, Uri contactUri) {
250 final ContactLoader loader =
251 new ContactLoader(context, contactUri, true /* postViewNotification */);
252 loader.registerListener(
253 0,
254 new OnLoadCompleteListener<Contact>() {
255 @Override
256 public void onLoadComplete(Loader<Contact> loader, Contact contact) {
257 try {
258 loader.reset();
259 } catch (RuntimeException e) {
260 LogUtil.e("CallerInfoUtils.onLoadComplete", "Error resetting loader", e);
261 }
262 }
263 });
264 loader.startLoading();
265 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700266
267 /** @return conference name for conference call. */
268 public static String getConferenceString(Context context, boolean isGenericConference) {
269 final int resId =
270 isGenericConference ? R.string.generic_conference_call_name : R.string.conference_call_name;
271 return context.getResources().getString(resId);
272 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800273}