blob: d7eea79bd3544c5c987f78e4bcf85ffb9ca6d2dd [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;
Eric Erfanianccca3152017-02-22 16:32:36 -080028import android.provider.ContactsContract.CommonDataKinds.Phone;
29import android.provider.ContactsContract.Contacts;
30import android.provider.ContactsContract.DisplayNameSources;
31import android.support.annotation.AnyThread;
32import android.support.annotation.MainThread;
33import android.support.annotation.NonNull;
Eric Erfaniand8046e52017-04-06 09:41:50 -070034import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080035import android.support.annotation.WorkerThread;
36import android.support.v4.os.UserManagerCompat;
37import android.telecom.TelecomManager;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070038import android.telephony.PhoneNumberUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080039import android.text.TextUtils;
40import android.util.ArrayMap;
41import android.util.ArraySet;
42import com.android.contacts.common.ContactsUtils;
43import com.android.dialer.common.Assert;
Eric Erfaniand8046e52017-04-06 09:41:50 -070044import com.android.dialer.common.concurrent.DialerExecutor;
45import com.android.dialer.common.concurrent.DialerExecutor.Worker;
46import com.android.dialer.common.concurrent.DialerExecutors;
Eric Erfanian8369df02017-05-03 10:27:13 -070047import com.android.dialer.logging.ContactLookupResult;
48import com.android.dialer.logging.ContactSource;
Eric Erfanian9779f962017-03-27 12:31:48 -070049import com.android.dialer.oem.CequintCallerIdManager;
50import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import com.android.dialer.phonenumbercache.CachedNumberLookupService;
52import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
53import com.android.dialer.phonenumbercache.ContactInfo;
54import com.android.dialer.phonenumbercache.PhoneNumberCache;
55import com.android.dialer.phonenumberutil.PhoneNumberHelper;
56import com.android.dialer.util.MoreStrings;
57import com.android.incallui.CallerInfoAsyncQuery.OnQueryCompleteListener;
58import com.android.incallui.ContactsAsyncHelper.OnImageLoadCompleteListener;
59import com.android.incallui.bindings.PhoneNumberService;
60import com.android.incallui.call.DialerCall;
61import com.android.incallui.incall.protocol.ContactPhotoType;
62import java.util.Map;
63import java.util.Objects;
64import java.util.Set;
65import java.util.concurrent.ConcurrentHashMap;
66import org.json.JSONException;
67import org.json.JSONObject;
68
69/**
70 * Class responsible for querying Contact Information for DialerCall objects. Can perform
71 * asynchronous requests to the Contact Provider for information as well as respond synchronously
72 * for any data that it currently has cached from previous queries. This class always gets called
73 * from the UI thread so it does not need thread protection.
74 */
75public class ContactInfoCache implements OnImageLoadCompleteListener {
76
77 private static final String TAG = ContactInfoCache.class.getSimpleName();
78 private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
79 private static ContactInfoCache sCache = null;
80 private final Context mContext;
81 private final PhoneNumberService mPhoneNumberService;
82 // Cache info map needs to be thread-safe since it could be modified by both main thread and
83 // worker thread.
Eric Erfaniand5e47f62017-03-15 14:41:07 -070084 private final ConcurrentHashMap<String, ContactCacheEntry> mInfoMap = new ConcurrentHashMap<>();
Eric Erfanianccca3152017-02-22 16:32:36 -080085 private final Map<String, Set<ContactInfoCacheCallback>> mCallBacks = new ArrayMap<>();
86 private Drawable mDefaultContactPhotoDrawable;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070087 private int mQueryId;
Eric Erfaniand8046e52017-04-06 09:41:50 -070088 private final DialerExecutor<CnapInformationWrapper> cachedNumberLookupExecutor =
89 DialerExecutors.createNonUiTaskBuilder(new CachedNumberLookupWorker()).build();
90
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) {
125 mContext = context;
126 mPhoneNumberService = Bindings.get(context).newPhoneNumberService(context);
127 }
128
129 public static synchronized ContactInfoCache getInstance(Context mContext) {
130 if (sCache == null) {
131 sCache = new ContactInfoCache(mContext.getApplicationContext());
132 }
133 return sCache;
134 }
135
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700136 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800137 Context context, DialerCall call, boolean isIncoming) {
138 final ContactCacheEntry entry = new ContactCacheEntry();
139
140 // TODO: get rid of caller info.
141 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
Eric Erfanian8369df02017-05-03 10:27:13 -0700142 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800143 return entry;
144 }
145
146 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700147 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 @NonNull Context context,
149 @NonNull CallerInfo info,
150 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700151 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800152 Objects.requireNonNull(info);
153 String displayName = null;
154 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800155 String label = null;
156 boolean isSipCall = false;
157
158 // It appears that there is a small change in behaviour with the
159 // PhoneUtils' startGetCallerInfo whereby if we query with an
160 // empty number, we will get a valid CallerInfo object, but with
161 // fields that are all null, and the isTemporary boolean input
162 // parameter as true.
163
164 // In the past, we would see a NULL callerinfo object, but this
165 // ends up causing null pointer exceptions elsewhere down the
166 // line in other cases, so we need to make this fix instead. It
167 // appears that this was the ONLY call to PhoneUtils
168 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
169 // an unknown contact.
170
171 // Currently, info.phoneNumber may actually be a SIP address, and
172 // if so, it might sometimes include the "sip:" prefix. That
173 // prefix isn't really useful to the user, though, so strip it off
174 // if present. (For any other URI scheme, though, leave the
175 // prefix alone.)
176 // TODO: It would be cleaner for CallerInfo to explicitly support
177 // SIP addresses instead of overloading the "phoneNumber" field.
178 // Then we could remove this hack, and instead ask the CallerInfo
179 // for a "user visible" form of the SIP address.
180 String number = info.phoneNumber;
181
182 if (!TextUtils.isEmpty(number)) {
183 isSipCall = PhoneNumberHelper.isUriNumber(number);
184 if (number.startsWith("sip:")) {
185 number = number.substring(4);
186 }
187 }
188
189 if (TextUtils.isEmpty(info.name)) {
190 // No valid "name" in the CallerInfo, so fall back to
191 // something else.
192 // (Typically, we promote the phone number up to the "name" slot
193 // onscreen, and possibly display a descriptive string in the
194 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700195 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800196 // No name *or* number! Display a generic "unknown" string
197 // (or potentially some other default based on the presentation.)
198 displayName = getPresentationString(context, presentation, info.callSubject);
199 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
200 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
201 // This case should never happen since the network should never send a phone #
202 // AND a restricted presentation. However we leave it here in case of weird
203 // network behavior
204 displayName = getPresentationString(context, presentation, info.callSubject);
205 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
206 } else if (!TextUtils.isEmpty(info.cnapName)) {
207 // No name, but we do have a valid CNAP name, so use that.
208 displayName = info.cnapName;
209 info.name = info.cnapName;
210 displayNumber = PhoneNumberHelper.formatNumber(number, context);
211 Log.d(
212 TAG,
213 " ==> cnapName available: displayName '"
214 + displayName
215 + "', displayNumber '"
216 + displayNumber
217 + "'");
218 } else {
219 // No name; all we have is a number. This is the typical
220 // case when an incoming call doesn't match any contact,
221 // or if you manually dial an outgoing number using the
222 // dialpad.
223 displayNumber = PhoneNumberHelper.formatNumber(number, context);
224
Eric Erfanianccca3152017-02-22 16:32:36 -0800225 Log.d(
226 TAG,
227 " ==> no name; falling back to number:"
228 + " displayNumber '"
229 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800230 + "'");
231 }
232 } else {
233 // We do have a valid "name" in the CallerInfo. Display that
234 // in the "name" slot, and the phone number in the "number" slot.
235 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
236 // This case should never happen since the network should never send a name
237 // AND a restricted presentation. However we leave it here in case of weird
238 // network behavior
239 displayName = getPresentationString(context, presentation, info.callSubject);
240 Log.d(
241 TAG,
242 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
243 } else {
244 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
245 // later determine whether to use the name or nameAlternative when presenting
246 displayName = info.name;
247 cce.nameAlternative = info.nameAlternative;
248 displayNumber = PhoneNumberHelper.formatNumber(number, context);
249 label = info.phoneLabel;
250 Log.d(
251 TAG,
252 " ==> name is present in CallerInfo: displayName '"
253 + displayName
254 + "', displayNumber '"
255 + displayNumber
256 + "'");
257 }
258 }
259
260 cce.namePrimary = displayName;
261 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700262 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800263 cce.label = label;
264 cce.isSipCall = isSipCall;
265 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700266 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700267 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800268
269 if (info.contactExists) {
270 cce.contactLookupResult = ContactLookupResult.Type.LOCAL_CONTACT;
271 }
272 }
273
274 /** Gets name strings based on some special presentation modes and the associated custom label. */
275 private static String getPresentationString(
276 Context context, int presentation, String customLabel) {
277 String name = context.getString(R.string.unknown);
278 if (!TextUtils.isEmpty(customLabel)
279 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
280 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
281 name = customLabel;
282 return name;
283 } else {
284 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
285 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context).toString();
286 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
287 name = context.getString(R.string.payphone);
288 }
289 }
290 return name;
291 }
292
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700293 ContactCacheEntry getInfo(String callId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800294 return mInfoMap.get(callId);
295 }
296
Eric Erfaniand8046e52017-04-06 09:41:50 -0700297 private static final class CnapInformationWrapper {
298 final String number;
299 final String cnapName;
300 final Context context;
301 final CachedNumberLookupService service;
302
303 CnapInformationWrapper(
304 String number, String cnapName, Context context, CachedNumberLookupService service) {
305 this.number = number;
306 this.cnapName = cnapName;
307 this.context = context;
308 this.service = service;
309 }
310 }
311
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700312 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800313 Context context, final DialerCall call, final CallerInfo info) {
314 final CachedNumberLookupService cachedNumberLookupService =
315 PhoneNumberCache.get(context).getCachedNumberLookupService();
316 if (!UserManagerCompat.isUserUnlocked(context)) {
317 Log.i(TAG, "User locked, not inserting cnap info into cache");
318 return;
319 }
320 if (cachedNumberLookupService == null
321 || TextUtils.isEmpty(info.cnapName)
322 || mInfoMap.get(call.getId()) != null) {
323 return;
324 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800325 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700326
327 cachedNumberLookupExecutor.executeParallel(
328 new CnapInformationWrapper(
329 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 }
331
332 /**
333 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
334 * If callback is null, no response is made, however the query is still performed and cached.
335 *
336 * @param callback The function to call back when the call is found. Can be null.
337 */
338 @MainThread
339 public void findInfo(
340 @NonNull final DialerCall call,
341 final boolean isIncoming,
342 @NonNull ContactInfoCacheCallback callback) {
343 Assert.isMainThread();
344 Objects.requireNonNull(callback);
345
346 final String callId = call.getId();
347 final ContactCacheEntry cacheEntry = mInfoMap.get(callId);
348 Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
349
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700350 // We need to force a new query if phone number has changed.
351 boolean forceQuery = needForceQuery(call, cacheEntry);
352 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
353
354 // If we have a previously obtained intermediate result return that now except needs
355 // force query.
356 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800357 Log.d(
358 TAG,
359 "Contact lookup. In memory cache hit; lookup "
360 + (callBacks == null ? "complete" : "still running"));
361 callback.onContactInfoComplete(callId, cacheEntry);
362 // If no other callbacks are in flight, we're done.
363 if (callBacks == null) {
364 return;
365 }
366 }
367
368 // If the entry already exists, add callback
369 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700370 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800371 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700372 if (!forceQuery) {
373 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
374 return;
375 }
376 } else {
377 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
378 // New lookup
379 callBacks = new ArraySet<>();
380 callBacks.add(callback);
381 mCallBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800382 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800383
384 /**
385 * Performs a query for caller information. Save any immediate data we get from the query. An
386 * asynchronous query may also be made for any data that we do not already have. Some queries,
387 * such as those for voicemail and emergency call information, will not perform an additional
388 * asynchronous query.
389 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700390 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryId, callId);
391 mQueryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800392 final CallerInfo callerInfo =
393 CallerInfoUtils.getCallerInfoForCall(
394 mContext,
395 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700396 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700397 new FindInfoCallback(isIncoming, queryToken));
Eric Erfanianccca3152017-02-22 16:32:36 -0800398
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700399 if (cacheEntry != null) {
400 // We should not override the old cache item until the new query is
401 // back. We should only update the queryId. Otherwise, we may see
402 // flicker of the name and image (old cache -> new cache before query
403 // -> new cache after query)
404 cacheEntry.queryId = queryToken.mQueryId;
405 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
406 } else {
407 ContactCacheEntry initialCacheEntry =
408 updateCallerInfoInCacheOnAnyThread(
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700409 callId, call.getNumberPresentation(), callerInfo, false, queryToken);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700410 sendInfoNotifications(callId, initialCacheEntry);
411 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800412 }
413
414 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700415 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800416 String callId,
417 int numberPresentation,
418 CallerInfo callerInfo,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700419 boolean didLocalLookup,
420 CallerInfoQueryToken queryToken) {
421 Log.d(
422 TAG,
423 "updateCallerInfoInCacheOnAnyThread: callId = "
424 + callId
425 + "; queryId = "
426 + queryToken.mQueryId
427 + "; didLocalLookup = "
428 + didLocalLookup);
429
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 int presentationMode = numberPresentation;
431 if (callerInfo.contactExists
432 || callerInfo.isEmergencyNumber()
433 || callerInfo.isVoiceMailNumber()) {
434 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
435 }
436
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700437 // We always replace the entry. The only exception is the same photo case.
Eric Erfanian8369df02017-05-03 10:27:13 -0700438 ContactCacheEntry cacheEntry = buildEntry(mContext, callerInfo, presentationMode);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700439 cacheEntry.queryId = queryToken.mQueryId;
440
441 ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
442 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
443
444 if (didLocalLookup) {
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700445 if (cacheEntry.displayPhotoUri != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700446 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
447 // we will still trigger force query so that the number can be updated on
448 // the calling screen. We need not query the image again if the previous
449 // query already has the image to avoid flickering.
450 if (existingCacheEntry != null
451 && existingCacheEntry.displayPhotoUri != null
452 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
453 && existingCacheEntry.photo != null) {
454 Log.d(TAG, "Same picture. Do not need start image load.");
455 cacheEntry.photo = existingCacheEntry.photo;
456 cacheEntry.photoType = existingCacheEntry.photoType;
457 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800458 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700459
460 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
461 // Load the image with a callback to update the image state.
462 // When the load is finished, onImageLoadComplete() will be called.
463 cacheEntry.hasPendingQuery = true;
464 ContactsAsyncHelper.startObtainPhotoAsync(
465 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
466 mContext,
467 cacheEntry.displayPhotoUri,
468 ContactInfoCache.this,
469 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700471 Log.d(TAG, "put entry into map: " + cacheEntry);
472 mInfoMap.put(callId, cacheEntry);
473 } else {
474 // Don't overwrite if there is existing cache.
475 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
476 mInfoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800477 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700478 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 }
480
Eric Erfaniand8046e52017-04-06 09:41:50 -0700481 private void maybeUpdateFromCequintCallerId(
482 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700483 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
484 return;
485 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700486 if (callerInfo.phoneNumber == null) {
487 return;
488 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700489 CequintCallerIdContact cequintCallerIdContact =
490 CequintCallerIdManager.getCequintCallerIdContactForInCall(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700491 mContext, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700492
Eric Erfaniand8046e52017-04-06 09:41:50 -0700493 if (cequintCallerIdContact == null) {
494 return;
495 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700496 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700497
498 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700499 callerInfo.name = cequintCallerIdContact.name;
Eric Erfanian8369df02017-05-03 10:27:13 -0700500 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700501 }
502 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
503 callerInfo.geoDescription = cequintCallerIdContact.geoDescription;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700504 callerInfo.shouldShowGeoDescription = true;
Eric Erfanian8369df02017-05-03 10:27:13 -0700505 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700506 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700507 // Don't overwrite photo in local contacts.
508 if (!callerInfo.contactExists
509 && callerInfo.contactDisplayPhotoUri == null
510 && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700511 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfanian8369df02017-05-03 10:27:13 -0700512 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700513 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700514 // Set contact to exist to avoid phone number service lookup.
515 callerInfo.contactExists = hasUpdate;
Eric Erfanian9779f962017-03-27 12:31:48 -0700516 }
517
Eric Erfanianccca3152017-02-22 16:32:36 -0800518 /**
519 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
520 * when image is loaded in worker thread.
521 */
522 @WorkerThread
523 @Override
524 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
525 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700526 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
527 final String callId = myCookie.mCallId;
528 final int queryId = myCookie.mQueryId;
529 if (!isWaitingForThisQuery(callId, queryId)) {
530 return;
531 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800532 loadImage(photo, photoIcon, cookie);
533 }
534
535 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700536 Log.d(TAG, "Image load complete with context: ", mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800537 // TODO: may be nice to update the image view again once the newer one
538 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700539 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
540 final String callId = myCookie.mCallId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800541 ContactCacheEntry entry = mInfoMap.get(callId);
542
543 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700544 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800545 clearCallbacks(callId);
546 return;
547 }
548
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700549 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800550
551 // Conference call icons are being handled in CallCardPresenter.
552 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700553 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800554 entry.photo = photo;
555 entry.photoType = ContactPhotoType.CONTACT;
556 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700557 Log.v(TAG, "photo icon: ", photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800558 entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
559 entry.photoType = ContactPhotoType.CONTACT;
560 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700561 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 entry.photo = null;
563 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
564 }
565 }
566
567 /**
568 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
569 * call state is reflected after the image is loaded.
570 */
571 @MainThread
572 @Override
573 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
574 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700575 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
576 final String callId = myCookie.mCallId;
577 final int queryId = myCookie.mQueryId;
578 if (!isWaitingForThisQuery(callId, queryId)) {
579 return;
580 }
581 sendImageNotifications(callId, mInfoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800582
583 clearCallbacks(callId);
584 }
585
586 /** Blows away the stored cache values. */
587 public void clearCache() {
588 mInfoMap.clear();
589 mCallBacks.clear();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700590 mQueryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800591 }
592
Eric Erfanian8369df02017-05-03 10:27:13 -0700593 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800594 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700595 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800596
597 // This will only be true for emergency numbers
598 if (info.photoResource != 0) {
599 cce.photo = context.getResources().getDrawable(info.photoResource);
600 } else if (info.isCachedPhotoCurrent) {
601 if (info.cachedPhoto != null) {
602 cce.photo = info.cachedPhoto;
603 cce.photoType = ContactPhotoType.CONTACT;
604 } else {
605 cce.photo = getDefaultContactPhotoDrawable();
606 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
607 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800608 } else {
609 cce.displayPhotoUri = info.contactDisplayPhotoUri;
610 cce.photo = null;
611 }
612
613 // Support any contact id in N because QuickContacts in N starts supporting enterprise
614 // contact id
615 if (info.lookupKeyOrNull != null
616 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
617 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
618 } else {
619 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
620 cce.lookupUri = null;
621 }
622
623 cce.lookupKey = info.lookupKeyOrNull;
624 cce.contactRingtoneUri = info.contactRingtoneUri;
625 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
626 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
627 }
628
629 return cce;
630 }
631
632 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700633 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800634 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700635 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800636 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
637 if (callBacks != null) {
638 for (ContactInfoCacheCallback callBack : callBacks) {
639 callBack.onContactInfoComplete(callId, entry);
640 }
641 }
642 }
643
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700644 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800645 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700646 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800647 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
648 if (callBacks != null && entry.photo != null) {
649 for (ContactInfoCacheCallback callBack : callBacks) {
650 callBack.onImageLoadComplete(callId, entry);
651 }
652 }
653 }
654
655 private void clearCallbacks(String callId) {
656 mCallBacks.remove(callId);
657 }
658
659 public Drawable getDefaultContactPhotoDrawable() {
660 if (mDefaultContactPhotoDrawable == null) {
661 mDefaultContactPhotoDrawable =
662 mContext.getResources().getDrawable(R.drawable.img_no_image_automirrored);
663 }
664 return mDefaultContactPhotoDrawable;
665 }
666
Eric Erfanianccca3152017-02-22 16:32:36 -0800667 /** Callback interface for the contact query. */
668 public interface ContactInfoCacheCallback {
669
670 void onContactInfoComplete(String callId, ContactCacheEntry entry);
671
672 void onImageLoadComplete(String callId, ContactCacheEntry entry);
673 }
674
675 /** This is cached contact info, which should be the ONLY info used by UI. */
676 public static class ContactCacheEntry {
677
678 public String namePrimary;
679 public String nameAlternative;
680 public String number;
681 public String location;
682 public String label;
683 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700684 @ContactPhotoType int photoType;
685 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800686 // Note in cache entry whether this is a pending async loading action to know whether to
687 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700688 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 /** This will be used for the "view" notification. */
690 public Uri contactUri;
691 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700692 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800693
694 public Uri lookupUri; // Sent to NotificationMananger
695 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700696 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800697 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700698 Uri contactRingtoneUri;
699 /** Query id to identify the query session. */
700 int queryId;
701 /** The phone number without any changes to display to the user (ex: cnap...) */
702 String originalPhoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700703 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700704
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700705 boolean isBusiness;
Eric Erfanianccca3152017-02-22 16:32:36 -0800706
707 @Override
708 public String toString() {
709 return "ContactCacheEntry{"
710 + "name='"
711 + MoreStrings.toSafeString(namePrimary)
712 + '\''
713 + ", nameAlternative='"
714 + MoreStrings.toSafeString(nameAlternative)
715 + '\''
716 + ", number='"
717 + MoreStrings.toSafeString(number)
718 + '\''
719 + ", location='"
720 + MoreStrings.toSafeString(location)
721 + '\''
722 + ", label='"
723 + label
724 + '\''
725 + ", photo="
726 + photo
727 + ", isSipCall="
728 + isSipCall
729 + ", contactUri="
730 + contactUri
731 + ", displayPhotoUri="
732 + displayPhotoUri
733 + ", contactLookupResult="
734 + contactLookupResult
735 + ", userType="
736 + userType
737 + ", contactRingtoneUri="
738 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700739 + ", queryId="
740 + queryId
741 + ", originalPhoneNumber="
742 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700743 + ", shouldShowLocation="
744 + shouldShowLocation
Eric Erfanianccca3152017-02-22 16:32:36 -0800745 + '}';
746 }
747 }
748
749 private static final class DialerCallCookieWrapper {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700750 final String callId;
751 final int numberPresentation;
752 final String cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800753
Eric Erfaniand8046e52017-04-06 09:41:50 -0700754 DialerCallCookieWrapper(String callId, int numberPresentation, String cnapName) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800755 this.callId = callId;
756 this.numberPresentation = numberPresentation;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700757 this.cnapName = cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800758 }
759 }
760
761 private class FindInfoCallback implements OnQueryCompleteListener {
762
763 private final boolean mIsIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700764 private final CallerInfoQueryToken mQueryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800765
Eric Erfaniand8046e52017-04-06 09:41:50 -0700766 FindInfoCallback(boolean isIncoming, CallerInfoQueryToken queryToken) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800767 mIsIncoming = isIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700768 mQueryToken = queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800769 }
770
771 @Override
772 public void onDataLoaded(int token, Object cookie, CallerInfo ci) {
773 Assert.isWorkerThread();
774 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700775 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
776 return;
777 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700778 long start = SystemClock.uptimeMillis();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700779 maybeUpdateFromCequintCallerId(ci, cw.cnapName, mIsIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700780 long time = SystemClock.uptimeMillis() - start;
781 Log.d(TAG, "Cequint Caller Id look up takes " + time + " ms.");
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700782 updateCallerInfoInCacheOnAnyThread(cw.callId, cw.numberPresentation, ci, true, mQueryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800783 }
784
785 @Override
786 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
787 Assert.isMainThread();
788 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
789 String callId = cw.callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700790 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
791 return;
792 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800793 ContactCacheEntry cacheEntry = mInfoMap.get(callId);
794 // This may happen only when InCallPresenter attempt to cleanup.
795 if (cacheEntry == null) {
796 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
797 clearCallbacks(callId);
798 return;
799 }
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700800 // Before issuing a request for more data from other services, we only check that the
801 // contact wasn't found in the local DB. We don't check the if the cache entry already
802 // has a name because we allow overriding cnap data with data from other services.
803 if (!callerInfo.contactExists && mPhoneNumberService != null) {
804 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
805 final PhoneNumberServiceListener listener =
806 new PhoneNumberServiceListener(callId, mQueryToken.mQueryId);
807 cacheEntry.hasPendingQuery = true;
808 mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, mIsIncoming);
809 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800810 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700811 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800812 if (callerInfo.contactExists) {
813 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
814 } else {
815 Log.d(
816 TAG,
817 "Contact lookup done. Local contact not found and"
818 + " no remote lookup service available.");
819 }
820 clearCallbacks(callId);
821 }
822 }
823 }
824
825 class PhoneNumberServiceListener
826 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
827
828 private final String mCallId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700829 private final int mQueryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800830
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700831 PhoneNumberServiceListener(String callId, int queryId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800832 mCallId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700833 mQueryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 }
835
836 @Override
837 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700838 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
839 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
840 return;
841 }
842
Eric Erfanianccca3152017-02-22 16:32:36 -0800843 // If we got a miss, this is the end of the lookup pipeline,
844 // so clear the callbacks and return.
845 if (info == null) {
846 Log.d(TAG, "Contact lookup done. Remote contact not found.");
847 clearCallbacks(mCallId);
848 return;
849 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800850 ContactCacheEntry entry = new ContactCacheEntry();
851 entry.namePrimary = info.getDisplayName();
852 entry.number = info.getNumber();
853 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700854 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 final int type = info.getPhoneType();
856 final String label = info.getPhoneLabel();
857 if (type == Phone.TYPE_CUSTOM) {
858 entry.label = label;
859 } else {
860 final CharSequence typeStr = Phone.getTypeLabel(mContext.getResources(), type, label);
861 entry.label = typeStr == null ? null : typeStr.toString();
862 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700863 final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
864 if (oldEntry != null) {
865 // Location is only obtained from local lookup so persist
866 // the value for remote lookups. Once we have a name this
867 // field is no longer used; it is persisted here in case
868 // the UI is ever changed to use it.
869 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700870 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700871 // Contact specific ringtone is obtained from local lookup.
872 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700873 entry.originalPhoneNumber = oldEntry.originalPhoneNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800874 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700875
876 // If no image and it's a business, switch to using the default business avatar.
877 if (info.getImageUrl() == null && info.isBusiness()) {
878 Log.d(TAG, "Business has no image. Using default.");
879 entry.photo = mContext.getResources().getDrawable(R.drawable.img_business);
880 entry.photoType = ContactPhotoType.BUSINESS;
881 }
882
883 Log.d(TAG, "put entry into map: " + entry);
884 mInfoMap.put(mCallId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800885 sendInfoNotifications(mCallId, entry);
886
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700887 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800888
889 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700890 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800891 // We're done, so clear callbacks
892 clearCallbacks(mCallId);
893 }
894 }
895
896 @Override
897 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700898 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
899 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
900 return;
901 }
902 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryIdOfRemoteLookup, mCallId);
903 loadImage(null, bitmap, queryToken);
904 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
905 }
906 }
907
908 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
909 if (call == null || call.isConferenceCall()) {
910 return false;
911 }
912
913 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
914 if (cacheEntry == null) {
915 // No info in the map yet so it is the 1st query
916 Log.d(TAG, "needForceQuery: first query");
917 return true;
918 }
919 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
920
921 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
922 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
923 return true;
924 }
925
926 return false;
927 }
928
929 private static final class CallerInfoQueryToken {
930 final int mQueryId;
931 final String mCallId;
932
933 CallerInfoQueryToken(int queryId, String callId) {
934 mQueryId = queryId;
935 mCallId = callId;
936 }
937 }
938
939 /** Check if the queryId in the cached map is the same as the one from query result. */
940 private boolean isWaitingForThisQuery(String callId, int queryId) {
941 final ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
942 if (existingCacheEntry == null) {
943 // This might happen if lookup on background thread comes back before the initial entry is
944 // created.
945 Log.d(TAG, "Cached entry is null.");
946 return true;
947 } else {
948 int waitingQueryId = existingCacheEntry.queryId;
949 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
950 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800951 }
952 }
953}