blob: 3014de363bbc0306ed14c539bff70ef8e3329826 [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;
29import 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;
37import android.support.v4.os.UserManagerCompat;
38import android.telecom.TelecomManager;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070039import android.telephony.PhoneNumberUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080040import android.text.TextUtils;
41import android.util.ArrayMap;
42import android.util.ArraySet;
43import com.android.contacts.common.ContactsUtils;
44import com.android.dialer.common.Assert;
Eric Erfaniand8046e52017-04-06 09:41:50 -070045import com.android.dialer.common.concurrent.DialerExecutor;
46import com.android.dialer.common.concurrent.DialerExecutor.Worker;
47import com.android.dialer.common.concurrent.DialerExecutors;
Eric Erfanian8369df02017-05-03 10:27:13 -070048import com.android.dialer.logging.ContactLookupResult;
49import com.android.dialer.logging.ContactSource;
Eric Erfanian9779f962017-03-27 12:31:48 -070050import com.android.dialer.oem.CequintCallerIdManager;
51import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import com.android.dialer.phonenumbercache.CachedNumberLookupService;
53import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
54import com.android.dialer.phonenumbercache.ContactInfo;
55import com.android.dialer.phonenumbercache.PhoneNumberCache;
56import com.android.dialer.phonenumberutil.PhoneNumberHelper;
57import com.android.dialer.util.MoreStrings;
58import com.android.incallui.CallerInfoAsyncQuery.OnQueryCompleteListener;
59import com.android.incallui.ContactsAsyncHelper.OnImageLoadCompleteListener;
60import com.android.incallui.bindings.PhoneNumberService;
61import com.android.incallui.call.DialerCall;
62import com.android.incallui.incall.protocol.ContactPhotoType;
63import java.util.Map;
64import java.util.Objects;
65import java.util.Set;
66import java.util.concurrent.ConcurrentHashMap;
67import org.json.JSONException;
68import org.json.JSONObject;
69
70/**
71 * Class responsible for querying Contact Information for DialerCall objects. Can perform
72 * asynchronous requests to the Contact Provider for information as well as respond synchronously
73 * for any data that it currently has cached from previous queries. This class always gets called
74 * from the UI thread so it does not need thread protection.
75 */
76public class ContactInfoCache implements OnImageLoadCompleteListener {
77
78 private static final String TAG = ContactInfoCache.class.getSimpleName();
79 private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
80 private static ContactInfoCache sCache = null;
81 private final Context mContext;
82 private final PhoneNumberService mPhoneNumberService;
83 // Cache info map needs to be thread-safe since it could be modified by both main thread and
84 // worker thread.
Eric Erfaniand5e47f62017-03-15 14:41:07 -070085 private final ConcurrentHashMap<String, ContactCacheEntry> mInfoMap = new ConcurrentHashMap<>();
Eric Erfanianccca3152017-02-22 16:32:36 -080086 private final Map<String, Set<ContactInfoCacheCallback>> mCallBacks = new ArrayMap<>();
87 private Drawable mDefaultContactPhotoDrawable;
88 private Drawable mConferencePhotoDrawable;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070089 private int mQueryId;
Eric Erfaniand8046e52017-04-06 09:41:50 -070090 private final DialerExecutor<CnapInformationWrapper> cachedNumberLookupExecutor =
91 DialerExecutors.createNonUiTaskBuilder(new CachedNumberLookupWorker()).build();
92
93 private static class CachedNumberLookupWorker implements Worker<CnapInformationWrapper, Void> {
94 @Nullable
95 @Override
96 public Void doInBackground(@Nullable CnapInformationWrapper input) {
97 if (input == null) {
98 return null;
99 }
100 ContactInfo contactInfo = new ContactInfo();
101 CachedContactInfo cacheInfo = input.service.buildCachedContactInfo(contactInfo);
Eric Erfanian8369df02017-05-03 10:27:13 -0700102 cacheInfo.setSource(ContactSource.Type.SOURCE_TYPE_CNAP, "CNAP", 0);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700103 contactInfo.name = input.cnapName;
104 contactInfo.number = input.number;
105 contactInfo.type = ContactsContract.CommonDataKinds.Phone.TYPE_MAIN;
106 try {
107 final JSONObject contactRows =
108 new JSONObject()
109 .put(
110 Phone.CONTENT_ITEM_TYPE,
111 new JSONObject()
112 .put(Phone.NUMBER, contactInfo.number)
113 .put(Phone.TYPE, Phone.TYPE_MAIN));
114 final String jsonString =
115 new JSONObject()
116 .put(Contacts.DISPLAY_NAME, contactInfo.name)
117 .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
118 .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
119 .toString();
120 cacheInfo.setLookupKey(jsonString);
121 } catch (JSONException e) {
122 Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
123 }
124 input.service.addContact(input.context.getApplicationContext(), cacheInfo);
125 return null;
126 }
127 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800128
129 private ContactInfoCache(Context context) {
130 mContext = context;
131 mPhoneNumberService = Bindings.get(context).newPhoneNumberService(context);
132 }
133
134 public static synchronized ContactInfoCache getInstance(Context mContext) {
135 if (sCache == null) {
136 sCache = new ContactInfoCache(mContext.getApplicationContext());
137 }
138 return sCache;
139 }
140
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700141 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800142 Context context, DialerCall call, boolean isIncoming) {
143 final ContactCacheEntry entry = new ContactCacheEntry();
144
145 // TODO: get rid of caller info.
146 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
Eric Erfanian8369df02017-05-03 10:27:13 -0700147 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 return entry;
149 }
150
151 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700152 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 @NonNull Context context,
154 @NonNull CallerInfo info,
155 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700156 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800157 Objects.requireNonNull(info);
158 String displayName = null;
159 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800160 String label = null;
161 boolean isSipCall = false;
162
163 // It appears that there is a small change in behaviour with the
164 // PhoneUtils' startGetCallerInfo whereby if we query with an
165 // empty number, we will get a valid CallerInfo object, but with
166 // fields that are all null, and the isTemporary boolean input
167 // parameter as true.
168
169 // In the past, we would see a NULL callerinfo object, but this
170 // ends up causing null pointer exceptions elsewhere down the
171 // line in other cases, so we need to make this fix instead. It
172 // appears that this was the ONLY call to PhoneUtils
173 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
174 // an unknown contact.
175
176 // Currently, info.phoneNumber may actually be a SIP address, and
177 // if so, it might sometimes include the "sip:" prefix. That
178 // prefix isn't really useful to the user, though, so strip it off
179 // if present. (For any other URI scheme, though, leave the
180 // prefix alone.)
181 // TODO: It would be cleaner for CallerInfo to explicitly support
182 // SIP addresses instead of overloading the "phoneNumber" field.
183 // Then we could remove this hack, and instead ask the CallerInfo
184 // for a "user visible" form of the SIP address.
185 String number = info.phoneNumber;
186
187 if (!TextUtils.isEmpty(number)) {
188 isSipCall = PhoneNumberHelper.isUriNumber(number);
189 if (number.startsWith("sip:")) {
190 number = number.substring(4);
191 }
192 }
193
194 if (TextUtils.isEmpty(info.name)) {
195 // No valid "name" in the CallerInfo, so fall back to
196 // something else.
197 // (Typically, we promote the phone number up to the "name" slot
198 // onscreen, and possibly display a descriptive string in the
199 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700200 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800201 // No name *or* number! Display a generic "unknown" string
202 // (or potentially some other default based on the presentation.)
203 displayName = getPresentationString(context, presentation, info.callSubject);
204 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
205 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
206 // This case should never happen since the network should never send a phone #
207 // AND a restricted presentation. However we leave it here in case of weird
208 // network behavior
209 displayName = getPresentationString(context, presentation, info.callSubject);
210 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
211 } else if (!TextUtils.isEmpty(info.cnapName)) {
212 // No name, but we do have a valid CNAP name, so use that.
213 displayName = info.cnapName;
214 info.name = info.cnapName;
215 displayNumber = PhoneNumberHelper.formatNumber(number, context);
216 Log.d(
217 TAG,
218 " ==> cnapName available: displayName '"
219 + displayName
220 + "', displayNumber '"
221 + displayNumber
222 + "'");
223 } else {
224 // No name; all we have is a number. This is the typical
225 // case when an incoming call doesn't match any contact,
226 // or if you manually dial an outgoing number using the
227 // dialpad.
228 displayNumber = PhoneNumberHelper.formatNumber(number, context);
229
Eric Erfanianccca3152017-02-22 16:32:36 -0800230 Log.d(
231 TAG,
232 " ==> no name; falling back to number:"
233 + " displayNumber '"
234 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800235 + "'");
236 }
237 } else {
238 // We do have a valid "name" in the CallerInfo. Display that
239 // in the "name" slot, and the phone number in the "number" slot.
240 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
241 // This case should never happen since the network should never send a name
242 // AND a restricted presentation. However we leave it here in case of weird
243 // network behavior
244 displayName = getPresentationString(context, presentation, info.callSubject);
245 Log.d(
246 TAG,
247 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
248 } else {
249 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
250 // later determine whether to use the name or nameAlternative when presenting
251 displayName = info.name;
252 cce.nameAlternative = info.nameAlternative;
253 displayNumber = PhoneNumberHelper.formatNumber(number, context);
254 label = info.phoneLabel;
255 Log.d(
256 TAG,
257 " ==> name is present in CallerInfo: displayName '"
258 + displayName
259 + "', displayNumber '"
260 + displayNumber
261 + "'");
262 }
263 }
264
265 cce.namePrimary = displayName;
266 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700267 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800268 cce.label = label;
269 cce.isSipCall = isSipCall;
270 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700271 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700272 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800273
274 if (info.contactExists) {
275 cce.contactLookupResult = ContactLookupResult.Type.LOCAL_CONTACT;
276 }
277 }
278
279 /** Gets name strings based on some special presentation modes and the associated custom label. */
280 private static String getPresentationString(
281 Context context, int presentation, String customLabel) {
282 String name = context.getString(R.string.unknown);
283 if (!TextUtils.isEmpty(customLabel)
284 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
285 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
286 name = customLabel;
287 return name;
288 } else {
289 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
290 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context).toString();
291 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
292 name = context.getString(R.string.payphone);
293 }
294 }
295 return name;
296 }
297
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700298 ContactCacheEntry getInfo(String callId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800299 return mInfoMap.get(callId);
300 }
301
Eric Erfaniand8046e52017-04-06 09:41:50 -0700302 private static final class CnapInformationWrapper {
303 final String number;
304 final String cnapName;
305 final Context context;
306 final CachedNumberLookupService service;
307
308 CnapInformationWrapper(
309 String number, String cnapName, Context context, CachedNumberLookupService service) {
310 this.number = number;
311 this.cnapName = cnapName;
312 this.context = context;
313 this.service = service;
314 }
315 }
316
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700317 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800318 Context context, final DialerCall call, final CallerInfo info) {
319 final CachedNumberLookupService cachedNumberLookupService =
320 PhoneNumberCache.get(context).getCachedNumberLookupService();
321 if (!UserManagerCompat.isUserUnlocked(context)) {
322 Log.i(TAG, "User locked, not inserting cnap info into cache");
323 return;
324 }
325 if (cachedNumberLookupService == null
326 || TextUtils.isEmpty(info.cnapName)
327 || mInfoMap.get(call.getId()) != null) {
328 return;
329 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700331
332 cachedNumberLookupExecutor.executeParallel(
333 new CnapInformationWrapper(
334 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800335 }
336
337 /**
338 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
339 * If callback is null, no response is made, however the query is still performed and cached.
340 *
341 * @param callback The function to call back when the call is found. Can be null.
342 */
343 @MainThread
344 public void findInfo(
345 @NonNull final DialerCall call,
346 final boolean isIncoming,
347 @NonNull ContactInfoCacheCallback callback) {
348 Assert.isMainThread();
349 Objects.requireNonNull(callback);
350
351 final String callId = call.getId();
352 final ContactCacheEntry cacheEntry = mInfoMap.get(callId);
353 Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
354
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700355 // We need to force a new query if phone number has changed.
356 boolean forceQuery = needForceQuery(call, cacheEntry);
357 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
358
359 // If we have a previously obtained intermediate result return that now except needs
360 // force query.
361 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800362 Log.d(
363 TAG,
364 "Contact lookup. In memory cache hit; lookup "
365 + (callBacks == null ? "complete" : "still running"));
366 callback.onContactInfoComplete(callId, cacheEntry);
367 // If no other callbacks are in flight, we're done.
368 if (callBacks == null) {
369 return;
370 }
371 }
372
373 // If the entry already exists, add callback
374 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700375 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800376 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700377 if (!forceQuery) {
378 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
379 return;
380 }
381 } else {
382 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
383 // New lookup
384 callBacks = new ArraySet<>();
385 callBacks.add(callback);
386 mCallBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800387 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800388
389 /**
390 * Performs a query for caller information. Save any immediate data we get from the query. An
391 * asynchronous query may also be made for any data that we do not already have. Some queries,
392 * such as those for voicemail and emergency call information, will not perform an additional
393 * asynchronous query.
394 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700395 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryId, callId);
396 mQueryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800397 final CallerInfo callerInfo =
398 CallerInfoUtils.getCallerInfoForCall(
399 mContext,
400 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700401 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700402 new FindInfoCallback(isIncoming, queryToken));
Eric Erfanianccca3152017-02-22 16:32:36 -0800403
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700404 if (cacheEntry != null) {
405 // We should not override the old cache item until the new query is
406 // back. We should only update the queryId. Otherwise, we may see
407 // flicker of the name and image (old cache -> new cache before query
408 // -> new cache after query)
409 cacheEntry.queryId = queryToken.mQueryId;
410 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
411 } else {
412 ContactCacheEntry initialCacheEntry =
413 updateCallerInfoInCacheOnAnyThread(
414 callId, call.getNumberPresentation(), callerInfo, isIncoming, false, queryToken);
415 sendInfoNotifications(callId, initialCacheEntry);
416 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800417 }
418
419 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700420 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800421 String callId,
422 int numberPresentation,
423 CallerInfo callerInfo,
424 boolean isIncoming,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700425 boolean didLocalLookup,
426 CallerInfoQueryToken queryToken) {
427 Log.d(
428 TAG,
429 "updateCallerInfoInCacheOnAnyThread: callId = "
430 + callId
431 + "; queryId = "
432 + queryToken.mQueryId
433 + "; didLocalLookup = "
434 + didLocalLookup);
435
Eric Erfanianccca3152017-02-22 16:32:36 -0800436 int presentationMode = numberPresentation;
437 if (callerInfo.contactExists
438 || callerInfo.isEmergencyNumber()
439 || callerInfo.isVoiceMailNumber()) {
440 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
441 }
442
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700443 // We always replace the entry. The only exception is the same photo case.
Eric Erfanian8369df02017-05-03 10:27:13 -0700444 ContactCacheEntry cacheEntry = buildEntry(mContext, callerInfo, presentationMode);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700445 cacheEntry.queryId = queryToken.mQueryId;
446
447 ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
448 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
449
450 if (didLocalLookup) {
451 // Before issuing a request for more data from other services, we only check that the
452 // contact wasn't found in the local DB. We don't check the if the cache entry already
453 // has a name because we allow overriding cnap data with data from other services.
454 if (!callerInfo.contactExists && mPhoneNumberService != null) {
455 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
456 final PhoneNumberServiceListener listener =
457 new PhoneNumberServiceListener(callId, queryToken.mQueryId);
458 cacheEntry.hasPendingQuery = true;
459 mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, isIncoming);
460 } else if (cacheEntry.displayPhotoUri != null) {
461 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
462 // we will still trigger force query so that the number can be updated on
463 // the calling screen. We need not query the image again if the previous
464 // query already has the image to avoid flickering.
465 if (existingCacheEntry != null
466 && existingCacheEntry.displayPhotoUri != null
467 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
468 && existingCacheEntry.photo != null) {
469 Log.d(TAG, "Same picture. Do not need start image load.");
470 cacheEntry.photo = existingCacheEntry.photo;
471 cacheEntry.photoType = existingCacheEntry.photoType;
472 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700474
475 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
476 // Load the image with a callback to update the image state.
477 // When the load is finished, onImageLoadComplete() will be called.
478 cacheEntry.hasPendingQuery = true;
479 ContactsAsyncHelper.startObtainPhotoAsync(
480 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
481 mContext,
482 cacheEntry.displayPhotoUri,
483 ContactInfoCache.this,
484 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800485 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700486 Log.d(TAG, "put entry into map: " + cacheEntry);
487 mInfoMap.put(callId, cacheEntry);
488 } else {
489 // Don't overwrite if there is existing cache.
490 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
491 mInfoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800492 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700493 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800494 }
495
Eric Erfaniand8046e52017-04-06 09:41:50 -0700496 private void maybeUpdateFromCequintCallerId(
497 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700498 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
499 return;
500 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700501 if (callerInfo.phoneNumber == null) {
502 return;
503 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700504 CequintCallerIdContact cequintCallerIdContact =
505 CequintCallerIdManager.getCequintCallerIdContactForInCall(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700506 mContext, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700507
Eric Erfaniand8046e52017-04-06 09:41:50 -0700508 if (cequintCallerIdContact == null) {
509 return;
510 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700511 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700512
513 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700514 callerInfo.name = cequintCallerIdContact.name;
Eric Erfanian8369df02017-05-03 10:27:13 -0700515 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700516 }
517 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
518 callerInfo.geoDescription = cequintCallerIdContact.geoDescription;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700519 callerInfo.shouldShowGeoDescription = true;
Eric Erfanian8369df02017-05-03 10:27:13 -0700520 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700521 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700522 // Don't overwrite photo in local contacts.
523 if (!callerInfo.contactExists
524 && callerInfo.contactDisplayPhotoUri == null
525 && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700526 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfanian8369df02017-05-03 10:27:13 -0700527 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700528 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700529 // Set contact to exist to avoid phone number service lookup.
530 callerInfo.contactExists = hasUpdate;
Eric Erfanian9779f962017-03-27 12:31:48 -0700531 }
532
Eric Erfanianccca3152017-02-22 16:32:36 -0800533 /**
534 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
535 * when image is loaded in worker thread.
536 */
537 @WorkerThread
538 @Override
539 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
540 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700541 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
542 final String callId = myCookie.mCallId;
543 final int queryId = myCookie.mQueryId;
544 if (!isWaitingForThisQuery(callId, queryId)) {
545 return;
546 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800547 loadImage(photo, photoIcon, cookie);
548 }
549
550 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700551 Log.d(TAG, "Image load complete with context: ", mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800552 // TODO: may be nice to update the image view again once the newer one
553 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700554 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
555 final String callId = myCookie.mCallId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 ContactCacheEntry entry = mInfoMap.get(callId);
557
558 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700559 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800560 clearCallbacks(callId);
561 return;
562 }
563
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700564 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800565
566 // Conference call icons are being handled in CallCardPresenter.
567 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700568 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800569 entry.photo = photo;
570 entry.photoType = ContactPhotoType.CONTACT;
571 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700572 Log.v(TAG, "photo icon: ", photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800573 entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
574 entry.photoType = ContactPhotoType.CONTACT;
575 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700576 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 entry.photo = null;
578 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
579 }
580 }
581
582 /**
583 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
584 * call state is reflected after the image is loaded.
585 */
586 @MainThread
587 @Override
588 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
589 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700590 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
591 final String callId = myCookie.mCallId;
592 final int queryId = myCookie.mQueryId;
593 if (!isWaitingForThisQuery(callId, queryId)) {
594 return;
595 }
596 sendImageNotifications(callId, mInfoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800597
598 clearCallbacks(callId);
599 }
600
601 /** Blows away the stored cache values. */
602 public void clearCache() {
603 mInfoMap.clear();
604 mCallBacks.clear();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700605 mQueryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800606 }
607
Eric Erfanian8369df02017-05-03 10:27:13 -0700608 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800609 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700610 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800611
612 // This will only be true for emergency numbers
613 if (info.photoResource != 0) {
614 cce.photo = context.getResources().getDrawable(info.photoResource);
615 } else if (info.isCachedPhotoCurrent) {
616 if (info.cachedPhoto != null) {
617 cce.photo = info.cachedPhoto;
618 cce.photoType = ContactPhotoType.CONTACT;
619 } else {
620 cce.photo = getDefaultContactPhotoDrawable();
621 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
622 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800623 } else {
624 cce.displayPhotoUri = info.contactDisplayPhotoUri;
625 cce.photo = null;
626 }
627
628 // Support any contact id in N because QuickContacts in N starts supporting enterprise
629 // contact id
630 if (info.lookupKeyOrNull != null
631 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
632 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
633 } else {
634 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
635 cce.lookupUri = null;
636 }
637
638 cce.lookupKey = info.lookupKeyOrNull;
639 cce.contactRingtoneUri = info.contactRingtoneUri;
640 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
641 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
642 }
643
644 return cce;
645 }
646
647 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700648 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800649 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700650 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800651 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
652 if (callBacks != null) {
653 for (ContactInfoCacheCallback callBack : callBacks) {
654 callBack.onContactInfoComplete(callId, entry);
655 }
656 }
657 }
658
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700659 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800660 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700661 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800662 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
663 if (callBacks != null && entry.photo != null) {
664 for (ContactInfoCacheCallback callBack : callBacks) {
665 callBack.onImageLoadComplete(callId, entry);
666 }
667 }
668 }
669
670 private void clearCallbacks(String callId) {
671 mCallBacks.remove(callId);
672 }
673
674 public Drawable getDefaultContactPhotoDrawable() {
675 if (mDefaultContactPhotoDrawable == null) {
676 mDefaultContactPhotoDrawable =
677 mContext.getResources().getDrawable(R.drawable.img_no_image_automirrored);
678 }
679 return mDefaultContactPhotoDrawable;
680 }
681
682 public Drawable getConferenceDrawable() {
683 if (mConferencePhotoDrawable == null) {
684 mConferencePhotoDrawable =
685 mContext.getResources().getDrawable(R.drawable.img_conference_automirrored);
686 }
687 return mConferencePhotoDrawable;
688 }
689
690 /** Callback interface for the contact query. */
691 public interface ContactInfoCacheCallback {
692
693 void onContactInfoComplete(String callId, ContactCacheEntry entry);
694
695 void onImageLoadComplete(String callId, ContactCacheEntry entry);
696 }
697
698 /** This is cached contact info, which should be the ONLY info used by UI. */
699 public static class ContactCacheEntry {
700
701 public String namePrimary;
702 public String nameAlternative;
703 public String number;
704 public String location;
705 public String label;
706 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700707 @ContactPhotoType int photoType;
708 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800709 // Note in cache entry whether this is a pending async loading action to know whether to
710 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700711 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 /** This will be used for the "view" notification. */
713 public Uri contactUri;
714 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700715 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800716
717 public Uri lookupUri; // Sent to NotificationMananger
718 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700719 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800720 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700721 Uri contactRingtoneUri;
722 /** Query id to identify the query session. */
723 int queryId;
724 /** The phone number without any changes to display to the user (ex: cnap...) */
725 String originalPhoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700726 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700727
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700728 boolean isBusiness;
Eric Erfanianccca3152017-02-22 16:32:36 -0800729
730 @Override
731 public String toString() {
732 return "ContactCacheEntry{"
733 + "name='"
734 + MoreStrings.toSafeString(namePrimary)
735 + '\''
736 + ", nameAlternative='"
737 + MoreStrings.toSafeString(nameAlternative)
738 + '\''
739 + ", number='"
740 + MoreStrings.toSafeString(number)
741 + '\''
742 + ", location='"
743 + MoreStrings.toSafeString(location)
744 + '\''
745 + ", label='"
746 + label
747 + '\''
748 + ", photo="
749 + photo
750 + ", isSipCall="
751 + isSipCall
752 + ", contactUri="
753 + contactUri
754 + ", displayPhotoUri="
755 + displayPhotoUri
756 + ", contactLookupResult="
757 + contactLookupResult
758 + ", userType="
759 + userType
760 + ", contactRingtoneUri="
761 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700762 + ", queryId="
763 + queryId
764 + ", originalPhoneNumber="
765 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700766 + ", shouldShowLocation="
767 + shouldShowLocation
Eric Erfanianccca3152017-02-22 16:32:36 -0800768 + '}';
769 }
770 }
771
772 private static final class DialerCallCookieWrapper {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700773 final String callId;
774 final int numberPresentation;
775 final String cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800776
Eric Erfaniand8046e52017-04-06 09:41:50 -0700777 DialerCallCookieWrapper(String callId, int numberPresentation, String cnapName) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800778 this.callId = callId;
779 this.numberPresentation = numberPresentation;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700780 this.cnapName = cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800781 }
782 }
783
784 private class FindInfoCallback implements OnQueryCompleteListener {
785
786 private final boolean mIsIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700787 private final CallerInfoQueryToken mQueryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800788
Eric Erfaniand8046e52017-04-06 09:41:50 -0700789 FindInfoCallback(boolean isIncoming, CallerInfoQueryToken queryToken) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800790 mIsIncoming = isIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700791 mQueryToken = queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800792 }
793
794 @Override
795 public void onDataLoaded(int token, Object cookie, CallerInfo ci) {
796 Assert.isWorkerThread();
797 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700798 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
799 return;
800 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700801 long start = SystemClock.uptimeMillis();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700802 maybeUpdateFromCequintCallerId(ci, cw.cnapName, mIsIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700803 long time = SystemClock.uptimeMillis() - start;
804 Log.d(TAG, "Cequint Caller Id look up takes " + time + " ms.");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700805 updateCallerInfoInCacheOnAnyThread(
806 cw.callId, cw.numberPresentation, ci, mIsIncoming, true, mQueryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800807 }
808
809 @Override
810 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
811 Assert.isMainThread();
812 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
813 String callId = cw.callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700814 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
815 return;
816 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800817 ContactCacheEntry cacheEntry = mInfoMap.get(callId);
818 // This may happen only when InCallPresenter attempt to cleanup.
819 if (cacheEntry == null) {
820 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
821 clearCallbacks(callId);
822 return;
823 }
824 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700825 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800826 if (callerInfo.contactExists) {
827 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
828 } else {
829 Log.d(
830 TAG,
831 "Contact lookup done. Local contact not found and"
832 + " no remote lookup service available.");
833 }
834 clearCallbacks(callId);
835 }
836 }
837 }
838
839 class PhoneNumberServiceListener
840 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
841
842 private final String mCallId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700843 private final int mQueryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800844
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700845 PhoneNumberServiceListener(String callId, int queryId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800846 mCallId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700847 mQueryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800848 }
849
850 @Override
851 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700852 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
853 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
854 return;
855 }
856
Eric Erfanianccca3152017-02-22 16:32:36 -0800857 // If we got a miss, this is the end of the lookup pipeline,
858 // so clear the callbacks and return.
859 if (info == null) {
860 Log.d(TAG, "Contact lookup done. Remote contact not found.");
861 clearCallbacks(mCallId);
862 return;
863 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800864 ContactCacheEntry entry = new ContactCacheEntry();
865 entry.namePrimary = info.getDisplayName();
866 entry.number = info.getNumber();
867 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700868 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800869 final int type = info.getPhoneType();
870 final String label = info.getPhoneLabel();
871 if (type == Phone.TYPE_CUSTOM) {
872 entry.label = label;
873 } else {
874 final CharSequence typeStr = Phone.getTypeLabel(mContext.getResources(), type, label);
875 entry.label = typeStr == null ? null : typeStr.toString();
876 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700877 final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
878 if (oldEntry != null) {
879 // Location is only obtained from local lookup so persist
880 // the value for remote lookups. Once we have a name this
881 // field is no longer used; it is persisted here in case
882 // the UI is ever changed to use it.
883 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700884 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700885 // Contact specific ringtone is obtained from local lookup.
886 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800887 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700888
889 // If no image and it's a business, switch to using the default business avatar.
890 if (info.getImageUrl() == null && info.isBusiness()) {
891 Log.d(TAG, "Business has no image. Using default.");
892 entry.photo = mContext.getResources().getDrawable(R.drawable.img_business);
893 entry.photoType = ContactPhotoType.BUSINESS;
894 }
895
896 Log.d(TAG, "put entry into map: " + entry);
897 mInfoMap.put(mCallId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800898 sendInfoNotifications(mCallId, entry);
899
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700900 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800901
902 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700903 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 // We're done, so clear callbacks
905 clearCallbacks(mCallId);
906 }
907 }
908
909 @Override
910 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700911 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
912 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
913 return;
914 }
915 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryIdOfRemoteLookup, mCallId);
916 loadImage(null, bitmap, queryToken);
917 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
918 }
919 }
920
921 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
922 if (call == null || call.isConferenceCall()) {
923 return false;
924 }
925
926 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
927 if (cacheEntry == null) {
928 // No info in the map yet so it is the 1st query
929 Log.d(TAG, "needForceQuery: first query");
930 return true;
931 }
932 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
933
934 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
935 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
936 return true;
937 }
938
939 return false;
940 }
941
942 private static final class CallerInfoQueryToken {
943 final int mQueryId;
944 final String mCallId;
945
946 CallerInfoQueryToken(int queryId, String callId) {
947 mQueryId = queryId;
948 mCallId = callId;
949 }
950 }
951
952 /** Check if the queryId in the cached map is the same as the one from query result. */
953 private boolean isWaitingForThisQuery(String callId, int queryId) {
954 final ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
955 if (existingCacheEntry == null) {
956 // This might happen if lookup on background thread comes back before the initial entry is
957 // created.
958 Log.d(TAG, "Cached entry is null.");
959 return true;
960 } else {
961 int waitingQueryId = existingCacheEntry.queryId;
962 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
963 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800964 }
965 }
966}