blob: 90513381e9aefba973d43e6b0a5b64791c4b542b [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
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
17package com.android.incallui;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.drawable.BitmapDrawable;
22import android.graphics.drawable.Drawable;
23import android.media.RingtoneManager;
24import android.net.Uri;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.os.Build.VERSION;
26import android.os.Build.VERSION_CODES;
Eric Erfanian9779f962017-03-27 12:31:48 -070027import android.os.SystemClock;
wangqicf61ca02017-08-31 15:32:55 -070028import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080029import android.provider.ContactsContract.CommonDataKinds.Phone;
30import android.provider.ContactsContract.Contacts;
31import android.provider.ContactsContract.DisplayNameSources;
32import android.support.annotation.AnyThread;
33import android.support.annotation.MainThread;
34import android.support.annotation.NonNull;
Eric Erfaniand8046e52017-04-06 09:41:50 -070035import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080036import android.support.annotation.WorkerThread;
Eric Erfanian2ca43182017-08-31 06:57:16 -070037import android.support.v4.content.ContextCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080038import android.support.v4.os.UserManagerCompat;
39import android.telecom.TelecomManager;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070040import android.telephony.PhoneNumberUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.text.TextUtils;
42import android.util.ArrayMap;
43import android.util.ArraySet;
44import com.android.contacts.common.ContactsUtils;
45import com.android.dialer.common.Assert;
Eric Erfaniand8046e52017-04-06 09:41:50 -070046import com.android.dialer.common.concurrent.DialerExecutor;
47import com.android.dialer.common.concurrent.DialerExecutor.Worker;
zachh0cd36a62017-10-31 12:04:05 -070048import com.android.dialer.common.concurrent.DialerExecutorComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070049import com.android.dialer.logging.ContactLookupResult;
50import com.android.dialer.logging.ContactSource;
Eric Erfanian9779f962017-03-27 12:31:48 -070051import com.android.dialer.oem.CequintCallerIdManager;
52import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.dialer.phonenumbercache.CachedNumberLookupService;
54import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
55import com.android.dialer.phonenumbercache.ContactInfo;
56import com.android.dialer.phonenumbercache.PhoneNumberCache;
57import com.android.dialer.phonenumberutil.PhoneNumberHelper;
58import com.android.dialer.util.MoreStrings;
59import com.android.incallui.CallerInfoAsyncQuery.OnQueryCompleteListener;
60import com.android.incallui.ContactsAsyncHelper.OnImageLoadCompleteListener;
61import com.android.incallui.bindings.PhoneNumberService;
62import com.android.incallui.call.DialerCall;
63import com.android.incallui.incall.protocol.ContactPhotoType;
64import java.util.Map;
65import java.util.Objects;
66import java.util.Set;
67import java.util.concurrent.ConcurrentHashMap;
68import org.json.JSONException;
69import org.json.JSONObject;
70
71/**
72 * Class responsible for querying Contact Information for DialerCall objects. Can perform
73 * asynchronous requests to the Contact Provider for information as well as respond synchronously
74 * for any data that it currently has cached from previous queries. This class always gets called
75 * from the UI thread so it does not need thread protection.
76 */
77public class ContactInfoCache implements OnImageLoadCompleteListener {
78
79 private static final String TAG = ContactInfoCache.class.getSimpleName();
80 private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
linyuh183cb712017-12-27 17:02:37 -080081 private static ContactInfoCache cache = null;
82 private final Context context;
83 private final PhoneNumberService phoneNumberService;
Eric Erfanianccca3152017-02-22 16:32:36 -080084 // Cache info map needs to be thread-safe since it could be modified by both main thread and
85 // worker thread.
linyuh183cb712017-12-27 17:02:37 -080086 private final ConcurrentHashMap<String, ContactCacheEntry> infoMap = new ConcurrentHashMap<>();
87 private final Map<String, Set<ContactInfoCacheCallback>> callBacks = new ArrayMap<>();
88 private int queryId;
zachh6a4cebd2017-10-24 17:10:06 -070089 private final DialerExecutor<CnapInformationWrapper> cachedNumberLookupExecutor;
Eric Erfaniand8046e52017-04-06 09:41:50 -070090
91 private static class CachedNumberLookupWorker implements Worker<CnapInformationWrapper, Void> {
92 @Nullable
93 @Override
94 public Void doInBackground(@Nullable CnapInformationWrapper input) {
95 if (input == null) {
96 return null;
97 }
98 ContactInfo contactInfo = new ContactInfo();
99 CachedContactInfo cacheInfo = input.service.buildCachedContactInfo(contactInfo);
Eric Erfanian8369df02017-05-03 10:27:13 -0700100 cacheInfo.setSource(ContactSource.Type.SOURCE_TYPE_CNAP, "CNAP", 0);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700101 contactInfo.name = input.cnapName;
102 contactInfo.number = input.number;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700103 try {
104 final JSONObject contactRows =
105 new JSONObject()
106 .put(
107 Phone.CONTENT_ITEM_TYPE,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700108 new JSONObject().put(Phone.NUMBER, contactInfo.number));
Eric Erfaniand8046e52017-04-06 09:41:50 -0700109 final String jsonString =
110 new JSONObject()
111 .put(Contacts.DISPLAY_NAME, contactInfo.name)
112 .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
113 .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
114 .toString();
115 cacheInfo.setLookupKey(jsonString);
116 } catch (JSONException e) {
117 Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
118 }
119 input.service.addContact(input.context.getApplicationContext(), cacheInfo);
120 return null;
121 }
122 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800123
124 private ContactInfoCache(Context context) {
wangqicf61ca02017-08-31 15:32:55 -0700125 Trace.beginSection("ContactInfoCache constructor");
linyuh183cb712017-12-27 17:02:37 -0800126 this.context = context;
127 phoneNumberService = Bindings.get(context).newPhoneNumberService(context);
zachh6a4cebd2017-10-24 17:10:06 -0700128 cachedNumberLookupExecutor =
linyuh183cb712017-12-27 17:02:37 -0800129 DialerExecutorComponent.get(this.context)
zachh0cd36a62017-10-31 12:04:05 -0700130 .dialerExecutorFactory()
131 .createNonUiTaskBuilder(new CachedNumberLookupWorker())
132 .build();
wangqicf61ca02017-08-31 15:32:55 -0700133 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800134 }
135
136 public static synchronized ContactInfoCache getInstance(Context mContext) {
linyuh183cb712017-12-27 17:02:37 -0800137 if (cache == null) {
138 cache = new ContactInfoCache(mContext.getApplicationContext());
Eric Erfanianccca3152017-02-22 16:32:36 -0800139 }
linyuh183cb712017-12-27 17:02:37 -0800140 return cache;
Eric Erfanianccca3152017-02-22 16:32:36 -0800141 }
142
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700143 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800144 Context context, DialerCall call, boolean isIncoming) {
145 final ContactCacheEntry entry = new ContactCacheEntry();
146
147 // TODO: get rid of caller info.
148 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
Eric Erfanian8369df02017-05-03 10:27:13 -0700149 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800150 return entry;
151 }
152
153 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700154 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800155 @NonNull Context context,
156 @NonNull CallerInfo info,
157 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700158 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800159 Objects.requireNonNull(info);
160 String displayName = null;
161 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800162 String label = null;
163 boolean isSipCall = false;
164
165 // It appears that there is a small change in behaviour with the
166 // PhoneUtils' startGetCallerInfo whereby if we query with an
167 // empty number, we will get a valid CallerInfo object, but with
168 // fields that are all null, and the isTemporary boolean input
169 // parameter as true.
170
171 // In the past, we would see a NULL callerinfo object, but this
172 // ends up causing null pointer exceptions elsewhere down the
173 // line in other cases, so we need to make this fix instead. It
174 // appears that this was the ONLY call to PhoneUtils
175 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
176 // an unknown contact.
177
178 // Currently, info.phoneNumber may actually be a SIP address, and
179 // if so, it might sometimes include the "sip:" prefix. That
180 // prefix isn't really useful to the user, though, so strip it off
181 // if present. (For any other URI scheme, though, leave the
182 // prefix alone.)
183 // TODO: It would be cleaner for CallerInfo to explicitly support
184 // SIP addresses instead of overloading the "phoneNumber" field.
185 // Then we could remove this hack, and instead ask the CallerInfo
186 // for a "user visible" form of the SIP address.
187 String number = info.phoneNumber;
188
189 if (!TextUtils.isEmpty(number)) {
190 isSipCall = PhoneNumberHelper.isUriNumber(number);
191 if (number.startsWith("sip:")) {
192 number = number.substring(4);
193 }
194 }
195
196 if (TextUtils.isEmpty(info.name)) {
197 // No valid "name" in the CallerInfo, so fall back to
198 // something else.
199 // (Typically, we promote the phone number up to the "name" slot
200 // onscreen, and possibly display a descriptive string in the
201 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700202 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800203 // No name *or* number! Display a generic "unknown" string
204 // (or potentially some other default based on the presentation.)
205 displayName = getPresentationString(context, presentation, info.callSubject);
206 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
207 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
208 // This case should never happen since the network should never send a phone #
209 // AND a restricted presentation. However we leave it here in case of weird
210 // network behavior
211 displayName = getPresentationString(context, presentation, info.callSubject);
212 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
213 } else if (!TextUtils.isEmpty(info.cnapName)) {
214 // No name, but we do have a valid CNAP name, so use that.
215 displayName = info.cnapName;
216 info.name = info.cnapName;
linyuhb06d0092018-03-01 15:05:36 -0800217 displayNumber = PhoneNumberHelper.formatNumber(context, number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 Log.d(
219 TAG,
220 " ==> cnapName available: displayName '"
221 + displayName
222 + "', displayNumber '"
223 + displayNumber
224 + "'");
225 } else {
226 // No name; all we have is a number. This is the typical
227 // case when an incoming call doesn't match any contact,
228 // or if you manually dial an outgoing number using the
229 // dialpad.
linyuhb06d0092018-03-01 15:05:36 -0800230 displayNumber = PhoneNumberHelper.formatNumber(context, number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800231
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 Log.d(
233 TAG,
234 " ==> no name; falling back to number:"
235 + " displayNumber '"
236 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800237 + "'");
238 }
239 } else {
240 // We do have a valid "name" in the CallerInfo. Display that
241 // in the "name" slot, and the phone number in the "number" slot.
242 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
243 // This case should never happen since the network should never send a name
244 // AND a restricted presentation. However we leave it here in case of weird
245 // network behavior
246 displayName = getPresentationString(context, presentation, info.callSubject);
247 Log.d(
248 TAG,
249 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
250 } else {
251 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
252 // later determine whether to use the name or nameAlternative when presenting
253 displayName = info.name;
254 cce.nameAlternative = info.nameAlternative;
linyuhb06d0092018-03-01 15:05:36 -0800255 displayNumber = PhoneNumberHelper.formatNumber(context, number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800256 label = info.phoneLabel;
257 Log.d(
258 TAG,
259 " ==> name is present in CallerInfo: displayName '"
260 + displayName
261 + "', displayNumber '"
262 + displayNumber
263 + "'");
264 }
265 }
266
267 cce.namePrimary = displayName;
268 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700269 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800270 cce.label = label;
271 cce.isSipCall = isSipCall;
272 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700273 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700274 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700275 cce.isEmergencyNumber = info.isEmergencyNumber();
276 cce.isVoicemailNumber = info.isVoiceMailNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800277
278 if (info.contactExists) {
279 cce.contactLookupResult = ContactLookupResult.Type.LOCAL_CONTACT;
280 }
281 }
282
283 /** Gets name strings based on some special presentation modes and the associated custom label. */
284 private static String getPresentationString(
285 Context context, int presentation, String customLabel) {
286 String name = context.getString(R.string.unknown);
287 if (!TextUtils.isEmpty(customLabel)
288 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
289 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
290 name = customLabel;
291 return name;
292 } else {
293 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
zachh03b13192018-01-26 10:56:46 -0800294 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800295 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
296 name = context.getString(R.string.payphone);
297 }
298 }
299 return name;
300 }
301
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700302 ContactCacheEntry getInfo(String callId) {
linyuh183cb712017-12-27 17:02:37 -0800303 return infoMap.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800304 }
305
Eric Erfaniand8046e52017-04-06 09:41:50 -0700306 private static final class CnapInformationWrapper {
307 final String number;
308 final String cnapName;
309 final Context context;
310 final CachedNumberLookupService service;
311
312 CnapInformationWrapper(
313 String number, String cnapName, Context context, CachedNumberLookupService service) {
314 this.number = number;
315 this.cnapName = cnapName;
316 this.context = context;
317 this.service = service;
318 }
319 }
320
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700321 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800322 Context context, final DialerCall call, final CallerInfo info) {
323 final CachedNumberLookupService cachedNumberLookupService =
324 PhoneNumberCache.get(context).getCachedNumberLookupService();
325 if (!UserManagerCompat.isUserUnlocked(context)) {
326 Log.i(TAG, "User locked, not inserting cnap info into cache");
327 return;
328 }
329 if (cachedNumberLookupService == null
330 || TextUtils.isEmpty(info.cnapName)
linyuh183cb712017-12-27 17:02:37 -0800331 || infoMap.get(call.getId()) != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800332 return;
333 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800334 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700335
336 cachedNumberLookupExecutor.executeParallel(
337 new CnapInformationWrapper(
338 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800339 }
340
341 /**
342 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
343 * If callback is null, no response is made, however the query is still performed and cached.
344 *
345 * @param callback The function to call back when the call is found. Can be null.
346 */
347 @MainThread
348 public void findInfo(
349 @NonNull final DialerCall call,
350 final boolean isIncoming,
351 @NonNull ContactInfoCacheCallback callback) {
wangqicf61ca02017-08-31 15:32:55 -0700352 Trace.beginSection("ContactInfoCache.findInfo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800353 Assert.isMainThread();
354 Objects.requireNonNull(callback);
355
wangqic8cf79e2017-10-17 09:21:00 -0700356 Trace.beginSection("prepare callback");
Eric Erfanianccca3152017-02-22 16:32:36 -0800357 final String callId = call.getId();
linyuh183cb712017-12-27 17:02:37 -0800358 final ContactCacheEntry cacheEntry = infoMap.get(callId);
359 Set<ContactInfoCacheCallback> callBacks = this.callBacks.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800360
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700361 // We need to force a new query if phone number has changed.
362 boolean forceQuery = needForceQuery(call, cacheEntry);
wangqic8cf79e2017-10-17 09:21:00 -0700363 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700364 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
365
366 // If we have a previously obtained intermediate result return that now except needs
367 // force query.
368 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800369 Log.d(
370 TAG,
371 "Contact lookup. In memory cache hit; lookup "
372 + (callBacks == null ? "complete" : "still running"));
373 callback.onContactInfoComplete(callId, cacheEntry);
374 // If no other callbacks are in flight, we're done.
375 if (callBacks == null) {
wangqicf61ca02017-08-31 15:32:55 -0700376 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800377 return;
378 }
379 }
380
381 // If the entry already exists, add callback
382 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700383 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800384 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700385 if (!forceQuery) {
386 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
wangqicf61ca02017-08-31 15:32:55 -0700387 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700388 return;
389 }
390 } else {
391 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
392 // New lookup
393 callBacks = new ArraySet<>();
394 callBacks.add(callback);
linyuh183cb712017-12-27 17:02:37 -0800395 this.callBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800396 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800397
wangqic8cf79e2017-10-17 09:21:00 -0700398 Trace.beginSection("prepare query");
Eric Erfanianccca3152017-02-22 16:32:36 -0800399 /**
400 * Performs a query for caller information. Save any immediate data we get from the query. An
401 * asynchronous query may also be made for any data that we do not already have. Some queries,
402 * such as those for voicemail and emergency call information, will not perform an additional
403 * asynchronous query.
404 */
linyuh183cb712017-12-27 17:02:37 -0800405 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(queryId, callId);
406 queryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800407 final CallerInfo callerInfo =
408 CallerInfoUtils.getCallerInfoForCall(
linyuh183cb712017-12-27 17:02:37 -0800409 context,
Eric Erfanianccca3152017-02-22 16:32:36 -0800410 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700411 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700412 new FindInfoCallback(isIncoming, queryToken));
wangqic8cf79e2017-10-17 09:21:00 -0700413 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800414
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700415 if (cacheEntry != null) {
416 // We should not override the old cache item until the new query is
417 // back. We should only update the queryId. Otherwise, we may see
418 // flicker of the name and image (old cache -> new cache before query
419 // -> new cache after query)
linyuh183cb712017-12-27 17:02:37 -0800420 cacheEntry.queryId = queryToken.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700421 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
422 } else {
423 ContactCacheEntry initialCacheEntry =
424 updateCallerInfoInCacheOnAnyThread(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700425 callId, call.getNumberPresentation(), callerInfo, false, queryToken);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700426 sendInfoNotifications(callId, initialCacheEntry);
427 }
wangqicf61ca02017-08-31 15:32:55 -0700428 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 }
430
431 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700432 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800433 String callId,
434 int numberPresentation,
435 CallerInfo callerInfo,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700436 boolean didLocalLookup,
437 CallerInfoQueryToken queryToken) {
wangqicf61ca02017-08-31 15:32:55 -0700438 Trace.beginSection("ContactInfoCache.updateCallerInfoInCacheOnAnyThread");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700439 Log.d(
440 TAG,
441 "updateCallerInfoInCacheOnAnyThread: callId = "
442 + callId
443 + "; queryId = "
linyuh183cb712017-12-27 17:02:37 -0800444 + queryToken.queryId
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700445 + "; didLocalLookup = "
446 + didLocalLookup);
447
linyuh183cb712017-12-27 17:02:37 -0800448 ContactCacheEntry existingCacheEntry = infoMap.get(callId);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700449 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
450
451 // Mark it as emergency/voicemail if the cache exists and was emergency/voicemail before the
452 // number changed.
453 if (existingCacheEntry != null) {
454 if (existingCacheEntry.isEmergencyNumber) {
linyuh183cb712017-12-27 17:02:37 -0800455 callerInfo.markAsEmergency(context);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700456 } else if (existingCacheEntry.isVoicemailNumber) {
linyuh183cb712017-12-27 17:02:37 -0800457 callerInfo.markAsVoiceMail(context);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700458 }
459 }
460
Eric Erfanianccca3152017-02-22 16:32:36 -0800461 int presentationMode = numberPresentation;
462 if (callerInfo.contactExists
463 || callerInfo.isEmergencyNumber()
464 || callerInfo.isVoiceMailNumber()) {
465 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
466 }
467
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700468 // We always replace the entry. The only exception is the same photo case.
linyuh183cb712017-12-27 17:02:37 -0800469 ContactCacheEntry cacheEntry = buildEntry(context, callerInfo, presentationMode);
470 cacheEntry.queryId = queryToken.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700471
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700472 if (didLocalLookup) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700473 if (cacheEntry.displayPhotoUri != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700474 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
475 // we will still trigger force query so that the number can be updated on
476 // the calling screen. We need not query the image again if the previous
477 // query already has the image to avoid flickering.
478 if (existingCacheEntry != null
479 && existingCacheEntry.displayPhotoUri != null
480 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
481 && existingCacheEntry.photo != null) {
482 Log.d(TAG, "Same picture. Do not need start image load.");
483 cacheEntry.photo = existingCacheEntry.photo;
484 cacheEntry.photoType = existingCacheEntry.photoType;
485 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800486 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700487
488 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
489 // Load the image with a callback to update the image state.
490 // When the load is finished, onImageLoadComplete() will be called.
491 cacheEntry.hasPendingQuery = true;
492 ContactsAsyncHelper.startObtainPhotoAsync(
493 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
linyuh183cb712017-12-27 17:02:37 -0800494 context,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700495 cacheEntry.displayPhotoUri,
496 ContactInfoCache.this,
497 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800498 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700499 Log.d(TAG, "put entry into map: " + cacheEntry);
linyuh183cb712017-12-27 17:02:37 -0800500 infoMap.put(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700501 } else {
502 // Don't overwrite if there is existing cache.
503 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
linyuh183cb712017-12-27 17:02:37 -0800504 infoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800505 }
wangqicf61ca02017-08-31 15:32:55 -0700506 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700507 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800508 }
509
Eric Erfaniand8046e52017-04-06 09:41:50 -0700510 private void maybeUpdateFromCequintCallerId(
511 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800512 if (!CequintCallerIdManager.isCequintCallerIdEnabled(context)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700513 return;
514 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700515 if (callerInfo.phoneNumber == null) {
516 return;
517 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700518 CequintCallerIdContact cequintCallerIdContact =
519 CequintCallerIdManager.getCequintCallerIdContactForInCall(
linyuh183cb712017-12-27 17:02:37 -0800520 context, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700521
Eric Erfaniand8046e52017-04-06 09:41:50 -0700522 if (cequintCallerIdContact == null) {
523 return;
524 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700525 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700526
527 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700528 callerInfo.name = cequintCallerIdContact.name;
Eric Erfanian8369df02017-05-03 10:27:13 -0700529 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700530 }
531 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
532 callerInfo.geoDescription = cequintCallerIdContact.geoDescription;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700533 callerInfo.shouldShowGeoDescription = true;
Eric Erfanian8369df02017-05-03 10:27:13 -0700534 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700535 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700536 // Don't overwrite photo in local contacts.
537 if (!callerInfo.contactExists
538 && callerInfo.contactDisplayPhotoUri == null
539 && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700540 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfanian8369df02017-05-03 10:27:13 -0700541 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700542 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700543 // Set contact to exist to avoid phone number service lookup.
wangqid6b91d12018-01-30 16:59:03 -0800544 if (hasUpdate) {
545 callerInfo.contactExists = true;
546 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700547 }
548
Eric Erfanianccca3152017-02-22 16:32:36 -0800549 /**
550 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
551 * when image is loaded in worker thread.
552 */
553 @WorkerThread
554 @Override
555 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
556 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700557 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
linyuh183cb712017-12-27 17:02:37 -0800558 final String callId = myCookie.callId;
559 final int queryId = myCookie.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700560 if (!isWaitingForThisQuery(callId, queryId)) {
561 return;
562 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 loadImage(photo, photoIcon, cookie);
564 }
565
566 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
linyuh183cb712017-12-27 17:02:37 -0800567 Log.d(TAG, "Image load complete with context: ", context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 // TODO: may be nice to update the image view again once the newer one
569 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700570 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
linyuh183cb712017-12-27 17:02:37 -0800571 final String callId = myCookie.callId;
572 ContactCacheEntry entry = infoMap.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800573
574 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700575 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800576 clearCallbacks(callId);
577 return;
578 }
579
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700580 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800581
582 // Conference call icons are being handled in CallCardPresenter.
583 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700584 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800585 entry.photo = photo;
586 entry.photoType = ContactPhotoType.CONTACT;
587 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700588 Log.v(TAG, "photo icon: ", photoIcon);
linyuh183cb712017-12-27 17:02:37 -0800589 entry.photo = new BitmapDrawable(context.getResources(), photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800590 entry.photoType = ContactPhotoType.CONTACT;
591 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700592 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800593 entry.photo = null;
594 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
595 }
596 }
597
598 /**
599 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
600 * call state is reflected after the image is loaded.
601 */
602 @MainThread
603 @Override
604 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
605 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700606 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
linyuh183cb712017-12-27 17:02:37 -0800607 final String callId = myCookie.callId;
608 final int queryId = myCookie.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700609 if (!isWaitingForThisQuery(callId, queryId)) {
610 return;
611 }
linyuh183cb712017-12-27 17:02:37 -0800612 sendImageNotifications(callId, infoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800613
614 clearCallbacks(callId);
615 }
616
617 /** Blows away the stored cache values. */
618 public void clearCache() {
linyuh183cb712017-12-27 17:02:37 -0800619 infoMap.clear();
620 callBacks.clear();
621 queryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800622 }
623
Eric Erfanian8369df02017-05-03 10:27:13 -0700624 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800625 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700626 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800627
628 // This will only be true for emergency numbers
629 if (info.photoResource != 0) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700630 cce.photo = ContextCompat.getDrawable(context, info.photoResource);
Eric Erfanianccca3152017-02-22 16:32:36 -0800631 } else if (info.isCachedPhotoCurrent) {
632 if (info.cachedPhoto != null) {
633 cce.photo = info.cachedPhoto;
634 cce.photoType = ContactPhotoType.CONTACT;
635 } else {
Eric Erfanianccca3152017-02-22 16:32:36 -0800636 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
637 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800638 } else {
639 cce.displayPhotoUri = info.contactDisplayPhotoUri;
640 cce.photo = null;
641 }
642
643 // Support any contact id in N because QuickContacts in N starts supporting enterprise
644 // contact id
645 if (info.lookupKeyOrNull != null
646 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
647 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
648 } else {
649 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
650 cce.lookupUri = null;
651 }
652
653 cce.lookupKey = info.lookupKeyOrNull;
654 cce.contactRingtoneUri = info.contactRingtoneUri;
655 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
656 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
657 }
658
659 return cce;
660 }
661
662 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700663 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800664 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700665 Trace.beginSection("ContactInfoCache.sendInfoNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700666 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -0800667 final Set<ContactInfoCacheCallback> callBacks = this.callBacks.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800668 if (callBacks != null) {
669 for (ContactInfoCacheCallback callBack : callBacks) {
670 callBack.onContactInfoComplete(callId, entry);
671 }
672 }
wangqicf61ca02017-08-31 15:32:55 -0700673 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800674 }
675
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700676 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800677 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700678 Trace.beginSection("ContactInfoCache.sendImageNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700679 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -0800680 final Set<ContactInfoCacheCallback> callBacks = this.callBacks.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800681 if (callBacks != null && entry.photo != null) {
682 for (ContactInfoCacheCallback callBack : callBacks) {
683 callBack.onImageLoadComplete(callId, entry);
684 }
685 }
wangqicf61ca02017-08-31 15:32:55 -0700686 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800687 }
688
689 private void clearCallbacks(String callId) {
linyuh183cb712017-12-27 17:02:37 -0800690 callBacks.remove(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800691 }
692
Eric Erfanianccca3152017-02-22 16:32:36 -0800693 /** Callback interface for the contact query. */
694 public interface ContactInfoCacheCallback {
695
696 void onContactInfoComplete(String callId, ContactCacheEntry entry);
697
698 void onImageLoadComplete(String callId, ContactCacheEntry entry);
699 }
700
701 /** This is cached contact info, which should be the ONLY info used by UI. */
702 public static class ContactCacheEntry {
703
704 public String namePrimary;
705 public String nameAlternative;
706 public String number;
707 public String location;
708 public String label;
709 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700710 @ContactPhotoType int photoType;
711 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 // Note in cache entry whether this is a pending async loading action to know whether to
713 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700714 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700716 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800717
718 public Uri lookupUri; // Sent to NotificationMananger
719 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700720 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800721 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700722 Uri contactRingtoneUri;
723 /** Query id to identify the query session. */
724 int queryId;
725 /** The phone number without any changes to display to the user (ex: cnap...) */
726 String originalPhoneNumber;
zachh6a4cebd2017-10-24 17:10:06 -0700727
Eric Erfaniand8046e52017-04-06 09:41:50 -0700728 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700729
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700730 boolean isBusiness;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700731 boolean isEmergencyNumber;
732 boolean isVoicemailNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800733
wangqiae6c8ec2017-09-28 17:39:40 -0700734 public boolean isLocalContact() {
735 return contactLookupResult == ContactLookupResult.Type.LOCAL_CONTACT;
736 }
737
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 @Override
739 public String toString() {
740 return "ContactCacheEntry{"
741 + "name='"
742 + MoreStrings.toSafeString(namePrimary)
743 + '\''
744 + ", nameAlternative='"
745 + MoreStrings.toSafeString(nameAlternative)
746 + '\''
747 + ", number='"
748 + MoreStrings.toSafeString(number)
749 + '\''
750 + ", location='"
751 + MoreStrings.toSafeString(location)
752 + '\''
753 + ", label='"
754 + label
755 + '\''
756 + ", photo="
757 + photo
758 + ", isSipCall="
759 + isSipCall
Eric Erfanianccca3152017-02-22 16:32:36 -0800760 + ", displayPhotoUri="
761 + displayPhotoUri
762 + ", contactLookupResult="
763 + contactLookupResult
764 + ", userType="
765 + userType
766 + ", contactRingtoneUri="
767 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700768 + ", queryId="
769 + queryId
770 + ", originalPhoneNumber="
771 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700772 + ", shouldShowLocation="
773 + shouldShowLocation
Eric Erfanian2ca43182017-08-31 06:57:16 -0700774 + ", isEmergencyNumber="
775 + isEmergencyNumber
776 + ", isVoicemailNumber="
777 + isVoicemailNumber
Eric Erfanianccca3152017-02-22 16:32:36 -0800778 + '}';
779 }
780 }
781
782 private static final class DialerCallCookieWrapper {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700783 final String callId;
784 final int numberPresentation;
785 final String cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800786
Eric Erfaniand8046e52017-04-06 09:41:50 -0700787 DialerCallCookieWrapper(String callId, int numberPresentation, String cnapName) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800788 this.callId = callId;
789 this.numberPresentation = numberPresentation;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700790 this.cnapName = cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800791 }
792 }
793
794 private class FindInfoCallback implements OnQueryCompleteListener {
795
linyuh183cb712017-12-27 17:02:37 -0800796 private final boolean isIncoming;
797 private final CallerInfoQueryToken queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800798
Eric Erfaniand8046e52017-04-06 09:41:50 -0700799 FindInfoCallback(boolean isIncoming, CallerInfoQueryToken queryToken) {
linyuh183cb712017-12-27 17:02:37 -0800800 this.isIncoming = isIncoming;
801 this.queryToken = queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800802 }
803
804 @Override
805 public void onDataLoaded(int token, Object cookie, CallerInfo ci) {
806 Assert.isWorkerThread();
807 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
linyuh183cb712017-12-27 17:02:37 -0800808 if (!isWaitingForThisQuery(cw.callId, queryToken.queryId)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700809 return;
810 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700811 long start = SystemClock.uptimeMillis();
linyuh183cb712017-12-27 17:02:37 -0800812 maybeUpdateFromCequintCallerId(ci, cw.cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700813 long time = SystemClock.uptimeMillis() - start;
814 Log.d(TAG, "Cequint Caller Id look up takes " + time + " ms.");
linyuh183cb712017-12-27 17:02:37 -0800815 updateCallerInfoInCacheOnAnyThread(cw.callId, cw.numberPresentation, ci, true, queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800816 }
817
818 @Override
819 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
wangqi9982f0d2017-10-11 17:46:07 -0700820 Trace.beginSection("ContactInfoCache.FindInfoCallback.onQueryComplete");
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 Assert.isMainThread();
822 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
823 String callId = cw.callId;
linyuh183cb712017-12-27 17:02:37 -0800824 if (!isWaitingForThisQuery(cw.callId, queryToken.queryId)) {
wangqi9982f0d2017-10-11 17:46:07 -0700825 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700826 return;
827 }
linyuh183cb712017-12-27 17:02:37 -0800828 ContactCacheEntry cacheEntry = infoMap.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800829 // This may happen only when InCallPresenter attempt to cleanup.
830 if (cacheEntry == null) {
831 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
832 clearCallbacks(callId);
wangqi9982f0d2017-10-11 17:46:07 -0700833 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 return;
835 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700836 // Before issuing a request for more data from other services, we only check that the
837 // contact wasn't found in the local DB. We don't check the if the cache entry already
838 // has a name because we allow overriding cnap data with data from other services.
linyuh183cb712017-12-27 17:02:37 -0800839 if (!callerInfo.contactExists && phoneNumberService != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700840 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
841 final PhoneNumberServiceListener listener =
linyuh183cb712017-12-27 17:02:37 -0800842 new PhoneNumberServiceListener(callId, queryToken.queryId);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700843 cacheEntry.hasPendingQuery = true;
linyuh183cb712017-12-27 17:02:37 -0800844 phoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, isIncoming);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700845 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800846 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700847 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800848 if (callerInfo.contactExists) {
849 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
850 } else {
851 Log.d(
852 TAG,
853 "Contact lookup done. Local contact not found and"
854 + " no remote lookup service available.");
855 }
856 clearCallbacks(callId);
857 }
wangqi9982f0d2017-10-11 17:46:07 -0700858 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800859 }
860 }
861
862 class PhoneNumberServiceListener
863 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
864
linyuh183cb712017-12-27 17:02:37 -0800865 private final String callId;
866 private final int queryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800867
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700868 PhoneNumberServiceListener(String callId, int queryId) {
linyuh183cb712017-12-27 17:02:37 -0800869 this.callId = callId;
870 queryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800871 }
872
873 @Override
874 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700875 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
linyuh183cb712017-12-27 17:02:37 -0800876 if (!isWaitingForThisQuery(callId, queryIdOfRemoteLookup)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700877 return;
878 }
879
Eric Erfanianccca3152017-02-22 16:32:36 -0800880 // If we got a miss, this is the end of the lookup pipeline,
881 // so clear the callbacks and return.
882 if (info == null) {
883 Log.d(TAG, "Contact lookup done. Remote contact not found.");
linyuh183cb712017-12-27 17:02:37 -0800884 clearCallbacks(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800885 return;
886 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800887 ContactCacheEntry entry = new ContactCacheEntry();
888 entry.namePrimary = info.getDisplayName();
889 entry.number = info.getNumber();
890 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700891 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800892 final int type = info.getPhoneType();
893 final String label = info.getPhoneLabel();
894 if (type == Phone.TYPE_CUSTOM) {
895 entry.label = label;
896 } else {
linyuh183cb712017-12-27 17:02:37 -0800897 final CharSequence typeStr = Phone.getTypeLabel(context.getResources(), type, label);
Eric Erfanianccca3152017-02-22 16:32:36 -0800898 entry.label = typeStr == null ? null : typeStr.toString();
899 }
linyuh183cb712017-12-27 17:02:37 -0800900 final ContactCacheEntry oldEntry = infoMap.get(callId);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700901 if (oldEntry != null) {
902 // Location is only obtained from local lookup so persist
903 // the value for remote lookups. Once we have a name this
904 // field is no longer used; it is persisted here in case
905 // the UI is ever changed to use it.
906 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700907 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700908 // Contact specific ringtone is obtained from local lookup.
909 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700910 entry.originalPhoneNumber = oldEntry.originalPhoneNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800911 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700912
913 // If no image and it's a business, switch to using the default business avatar.
914 if (info.getImageUrl() == null && info.isBusiness()) {
915 Log.d(TAG, "Business has no image. Using default.");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700916 entry.photoType = ContactPhotoType.BUSINESS;
917 }
918
919 Log.d(TAG, "put entry into map: " + entry);
linyuh183cb712017-12-27 17:02:37 -0800920 infoMap.put(callId, entry);
921 sendInfoNotifications(callId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800922
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700923 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800924
925 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700926 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800927 // We're done, so clear callbacks
linyuh183cb712017-12-27 17:02:37 -0800928 clearCallbacks(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800929 }
930 }
931
932 @Override
933 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700934 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
linyuh183cb712017-12-27 17:02:37 -0800935 if (!isWaitingForThisQuery(callId, queryIdOfRemoteLookup)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700936 return;
937 }
linyuh183cb712017-12-27 17:02:37 -0800938 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(queryIdOfRemoteLookup, callId);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700939 loadImage(null, bitmap, queryToken);
940 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
941 }
942 }
943
944 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
945 if (call == null || call.isConferenceCall()) {
946 return false;
947 }
948
949 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
950 if (cacheEntry == null) {
951 // No info in the map yet so it is the 1st query
952 Log.d(TAG, "needForceQuery: first query");
953 return true;
954 }
955 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
956
957 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
958 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
959 return true;
960 }
961
962 return false;
963 }
964
965 private static final class CallerInfoQueryToken {
linyuh183cb712017-12-27 17:02:37 -0800966 final int queryId;
967 final String callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700968
969 CallerInfoQueryToken(int queryId, String callId) {
linyuh183cb712017-12-27 17:02:37 -0800970 this.queryId = queryId;
971 this.callId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700972 }
973 }
974
975 /** Check if the queryId in the cached map is the same as the one from query result. */
976 private boolean isWaitingForThisQuery(String callId, int queryId) {
linyuh183cb712017-12-27 17:02:37 -0800977 final ContactCacheEntry existingCacheEntry = infoMap.get(callId);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700978 if (existingCacheEntry == null) {
979 // This might happen if lookup on background thread comes back before the initial entry is
980 // created.
981 Log.d(TAG, "Cached entry is null.");
982 return true;
983 } else {
984 int waitingQueryId = existingCacheEntry.queryId;
985 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
986 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800987 }
988 }
989}