blob: 9e696b55e6260ca1e60683b63a91dd3752276d3f [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 Erfanianccca3152017-02-22 16:32:36 -080048import com.android.dialer.logging.nano.ContactLookupResult;
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;
87 private Drawable mConferencePhotoDrawable;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070088 private int mQueryId;
Eric Erfaniand8046e52017-04-06 09:41:50 -070089 private final DialerExecutor<CnapInformationWrapper> cachedNumberLookupExecutor =
90 DialerExecutors.createNonUiTaskBuilder(new CachedNumberLookupWorker()).build();
91
92 private static class CachedNumberLookupWorker implements Worker<CnapInformationWrapper, Void> {
93 @Nullable
94 @Override
95 public Void doInBackground(@Nullable CnapInformationWrapper input) {
96 if (input == null) {
97 return null;
98 }
99 ContactInfo contactInfo = new ContactInfo();
100 CachedContactInfo cacheInfo = input.service.buildCachedContactInfo(contactInfo);
101 cacheInfo.setSource(CachedContactInfo.SOURCE_TYPE_CNAP, "CNAP", 0);
102 contactInfo.name = input.cnapName;
103 contactInfo.number = input.number;
104 contactInfo.type = ContactsContract.CommonDataKinds.Phone.TYPE_MAIN;
105 try {
106 final JSONObject contactRows =
107 new JSONObject()
108 .put(
109 Phone.CONTENT_ITEM_TYPE,
110 new JSONObject()
111 .put(Phone.NUMBER, contactInfo.number)
112 .put(Phone.TYPE, Phone.TYPE_MAIN));
113 final String jsonString =
114 new JSONObject()
115 .put(Contacts.DISPLAY_NAME, contactInfo.name)
116 .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
117 .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
118 .toString();
119 cacheInfo.setLookupKey(jsonString);
120 } catch (JSONException e) {
121 Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
122 }
123 input.service.addContact(input.context.getApplicationContext(), cacheInfo);
124 return null;
125 }
126 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800127
128 private ContactInfoCache(Context context) {
129 mContext = context;
130 mPhoneNumberService = Bindings.get(context).newPhoneNumberService(context);
131 }
132
133 public static synchronized ContactInfoCache getInstance(Context mContext) {
134 if (sCache == null) {
135 sCache = new ContactInfoCache(mContext.getApplicationContext());
136 }
137 return sCache;
138 }
139
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700140 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800141 Context context, DialerCall call, boolean isIncoming) {
142 final ContactCacheEntry entry = new ContactCacheEntry();
143
144 // TODO: get rid of caller info.
145 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
146 ContactInfoCache.populateCacheEntry(
147 context, info, entry, call.getNumberPresentation(), isIncoming);
148 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,
156 int presentation,
157 boolean isIncoming) {
158 Objects.requireNonNull(info);
159 String displayName = null;
160 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800161 String label = null;
162 boolean isSipCall = false;
163
164 // It appears that there is a small change in behaviour with the
165 // PhoneUtils' startGetCallerInfo whereby if we query with an
166 // empty number, we will get a valid CallerInfo object, but with
167 // fields that are all null, and the isTemporary boolean input
168 // parameter as true.
169
170 // In the past, we would see a NULL callerinfo object, but this
171 // ends up causing null pointer exceptions elsewhere down the
172 // line in other cases, so we need to make this fix instead. It
173 // appears that this was the ONLY call to PhoneUtils
174 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
175 // an unknown contact.
176
177 // Currently, info.phoneNumber may actually be a SIP address, and
178 // if so, it might sometimes include the "sip:" prefix. That
179 // prefix isn't really useful to the user, though, so strip it off
180 // if present. (For any other URI scheme, though, leave the
181 // prefix alone.)
182 // TODO: It would be cleaner for CallerInfo to explicitly support
183 // SIP addresses instead of overloading the "phoneNumber" field.
184 // Then we could remove this hack, and instead ask the CallerInfo
185 // for a "user visible" form of the SIP address.
186 String number = info.phoneNumber;
187
188 if (!TextUtils.isEmpty(number)) {
189 isSipCall = PhoneNumberHelper.isUriNumber(number);
190 if (number.startsWith("sip:")) {
191 number = number.substring(4);
192 }
193 }
194
195 if (TextUtils.isEmpty(info.name)) {
196 // No valid "name" in the CallerInfo, so fall back to
197 // something else.
198 // (Typically, we promote the phone number up to the "name" slot
199 // onscreen, and possibly display a descriptive string in the
200 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700201 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800202 // No name *or* number! Display a generic "unknown" string
203 // (or potentially some other default based on the presentation.)
204 displayName = getPresentationString(context, presentation, info.callSubject);
205 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
206 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
207 // This case should never happen since the network should never send a phone #
208 // AND a restricted presentation. However we leave it here in case of weird
209 // network behavior
210 displayName = getPresentationString(context, presentation, info.callSubject);
211 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
212 } else if (!TextUtils.isEmpty(info.cnapName)) {
213 // No name, but we do have a valid CNAP name, so use that.
214 displayName = info.cnapName;
215 info.name = info.cnapName;
216 displayNumber = PhoneNumberHelper.formatNumber(number, context);
217 Log.d(
218 TAG,
219 " ==> cnapName available: displayName '"
220 + displayName
221 + "', displayNumber '"
222 + displayNumber
223 + "'");
224 } else {
225 // No name; all we have is a number. This is the typical
226 // case when an incoming call doesn't match any contact,
227 // or if you manually dial an outgoing number using the
228 // dialpad.
229 displayNumber = PhoneNumberHelper.formatNumber(number, context);
230
Eric Erfanianccca3152017-02-22 16:32:36 -0800231 Log.d(
232 TAG,
233 " ==> no name; falling back to number:"
234 + " displayNumber '"
235 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 + "'");
237 }
238 } else {
239 // We do have a valid "name" in the CallerInfo. Display that
240 // in the "name" slot, and the phone number in the "number" slot.
241 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
242 // This case should never happen since the network should never send a name
243 // AND a restricted presentation. However we leave it here in case of weird
244 // network behavior
245 displayName = getPresentationString(context, presentation, info.callSubject);
246 Log.d(
247 TAG,
248 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
249 } else {
250 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
251 // later determine whether to use the name or nameAlternative when presenting
252 displayName = info.name;
253 cce.nameAlternative = info.nameAlternative;
254 displayNumber = PhoneNumberHelper.formatNumber(number, context);
255 label = info.phoneLabel;
256 Log.d(
257 TAG,
258 " ==> name is present in CallerInfo: displayName '"
259 + displayName
260 + "', displayNumber '"
261 + displayNumber
262 + "'");
263 }
264 }
265
266 cce.namePrimary = displayName;
267 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700268 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800269 cce.label = label;
270 cce.isSipCall = isSipCall;
271 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700272 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700273 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800274
275 if (info.contactExists) {
276 cce.contactLookupResult = ContactLookupResult.Type.LOCAL_CONTACT;
277 }
278 }
279
280 /** Gets name strings based on some special presentation modes and the associated custom label. */
281 private static String getPresentationString(
282 Context context, int presentation, String customLabel) {
283 String name = context.getString(R.string.unknown);
284 if (!TextUtils.isEmpty(customLabel)
285 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
286 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
287 name = customLabel;
288 return name;
289 } else {
290 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
291 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context).toString();
292 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
293 name = context.getString(R.string.payphone);
294 }
295 }
296 return name;
297 }
298
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700299 ContactCacheEntry getInfo(String callId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800300 return mInfoMap.get(callId);
301 }
302
Eric Erfaniand8046e52017-04-06 09:41:50 -0700303 private static final class CnapInformationWrapper {
304 final String number;
305 final String cnapName;
306 final Context context;
307 final CachedNumberLookupService service;
308
309 CnapInformationWrapper(
310 String number, String cnapName, Context context, CachedNumberLookupService service) {
311 this.number = number;
312 this.cnapName = cnapName;
313 this.context = context;
314 this.service = service;
315 }
316 }
317
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700318 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800319 Context context, final DialerCall call, final CallerInfo info) {
320 final CachedNumberLookupService cachedNumberLookupService =
321 PhoneNumberCache.get(context).getCachedNumberLookupService();
322 if (!UserManagerCompat.isUserUnlocked(context)) {
323 Log.i(TAG, "User locked, not inserting cnap info into cache");
324 return;
325 }
326 if (cachedNumberLookupService == null
327 || TextUtils.isEmpty(info.cnapName)
328 || mInfoMap.get(call.getId()) != null) {
329 return;
330 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800331 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700332
333 cachedNumberLookupExecutor.executeParallel(
334 new CnapInformationWrapper(
335 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800336 }
337
338 /**
339 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
340 * If callback is null, no response is made, however the query is still performed and cached.
341 *
342 * @param callback The function to call back when the call is found. Can be null.
343 */
344 @MainThread
345 public void findInfo(
346 @NonNull final DialerCall call,
347 final boolean isIncoming,
348 @NonNull ContactInfoCacheCallback callback) {
349 Assert.isMainThread();
350 Objects.requireNonNull(callback);
351
352 final String callId = call.getId();
353 final ContactCacheEntry cacheEntry = mInfoMap.get(callId);
354 Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
355
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700356 // We need to force a new query if phone number has changed.
357 boolean forceQuery = needForceQuery(call, cacheEntry);
358 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
359
360 // If we have a previously obtained intermediate result return that now except needs
361 // force query.
362 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800363 Log.d(
364 TAG,
365 "Contact lookup. In memory cache hit; lookup "
366 + (callBacks == null ? "complete" : "still running"));
367 callback.onContactInfoComplete(callId, cacheEntry);
368 // If no other callbacks are in flight, we're done.
369 if (callBacks == null) {
370 return;
371 }
372 }
373
374 // If the entry already exists, add callback
375 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700376 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800377 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700378 if (!forceQuery) {
379 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
380 return;
381 }
382 } else {
383 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
384 // New lookup
385 callBacks = new ArraySet<>();
386 callBacks.add(callback);
387 mCallBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800388 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800389
390 /**
391 * Performs a query for caller information. Save any immediate data we get from the query. An
392 * asynchronous query may also be made for any data that we do not already have. Some queries,
393 * such as those for voicemail and emergency call information, will not perform an additional
394 * asynchronous query.
395 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700396 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryId, callId);
397 mQueryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800398 final CallerInfo callerInfo =
399 CallerInfoUtils.getCallerInfoForCall(
400 mContext,
401 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700402 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700403 new FindInfoCallback(isIncoming, queryToken));
Eric Erfanianccca3152017-02-22 16:32:36 -0800404
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700405 if (cacheEntry != null) {
406 // We should not override the old cache item until the new query is
407 // back. We should only update the queryId. Otherwise, we may see
408 // flicker of the name and image (old cache -> new cache before query
409 // -> new cache after query)
410 cacheEntry.queryId = queryToken.mQueryId;
411 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
412 } else {
413 ContactCacheEntry initialCacheEntry =
414 updateCallerInfoInCacheOnAnyThread(
415 callId, call.getNumberPresentation(), callerInfo, isIncoming, false, queryToken);
416 sendInfoNotifications(callId, initialCacheEntry);
417 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800418 }
419
420 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700421 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800422 String callId,
423 int numberPresentation,
424 CallerInfo callerInfo,
425 boolean isIncoming,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700426 boolean didLocalLookup,
427 CallerInfoQueryToken queryToken) {
428 Log.d(
429 TAG,
430 "updateCallerInfoInCacheOnAnyThread: callId = "
431 + callId
432 + "; queryId = "
433 + queryToken.mQueryId
434 + "; didLocalLookup = "
435 + didLocalLookup);
436
Eric Erfanianccca3152017-02-22 16:32:36 -0800437 int presentationMode = numberPresentation;
438 if (callerInfo.contactExists
439 || callerInfo.isEmergencyNumber()
440 || callerInfo.isVoiceMailNumber()) {
441 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
442 }
443
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700444 // We always replace the entry. The only exception is the same photo case.
445 ContactCacheEntry cacheEntry = buildEntry(mContext, callerInfo, presentationMode, isIncoming);
446 cacheEntry.queryId = queryToken.mQueryId;
447
448 ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
449 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
450
451 if (didLocalLookup) {
452 // Before issuing a request for more data from other services, we only check that the
453 // contact wasn't found in the local DB. We don't check the if the cache entry already
454 // has a name because we allow overriding cnap data with data from other services.
455 if (!callerInfo.contactExists && mPhoneNumberService != null) {
456 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
457 final PhoneNumberServiceListener listener =
458 new PhoneNumberServiceListener(callId, queryToken.mQueryId);
459 cacheEntry.hasPendingQuery = true;
460 mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, isIncoming);
461 } else if (cacheEntry.displayPhotoUri != null) {
462 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
463 // we will still trigger force query so that the number can be updated on
464 // the calling screen. We need not query the image again if the previous
465 // query already has the image to avoid flickering.
466 if (existingCacheEntry != null
467 && existingCacheEntry.displayPhotoUri != null
468 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
469 && existingCacheEntry.photo != null) {
470 Log.d(TAG, "Same picture. Do not need start image load.");
471 cacheEntry.photo = existingCacheEntry.photo;
472 cacheEntry.photoType = existingCacheEntry.photoType;
473 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800474 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700475
476 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
477 // Load the image with a callback to update the image state.
478 // When the load is finished, onImageLoadComplete() will be called.
479 cacheEntry.hasPendingQuery = true;
480 ContactsAsyncHelper.startObtainPhotoAsync(
481 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
482 mContext,
483 cacheEntry.displayPhotoUri,
484 ContactInfoCache.this,
485 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800486 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700487 Log.d(TAG, "put entry into map: " + cacheEntry);
488 mInfoMap.put(callId, cacheEntry);
489 } else {
490 // Don't overwrite if there is existing cache.
491 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
492 mInfoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800493 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700494 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800495 }
496
Eric Erfaniand8046e52017-04-06 09:41:50 -0700497 private void maybeUpdateFromCequintCallerId(
498 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700499 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
500 return;
501 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700502 if (callerInfo.phoneNumber == null) {
503 return;
504 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700505 CequintCallerIdContact cequintCallerIdContact =
506 CequintCallerIdManager.getCequintCallerIdContactForInCall(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700507 mContext, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700508
Eric Erfaniand8046e52017-04-06 09:41:50 -0700509 if (cequintCallerIdContact == null) {
510 return;
511 }
512
513 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700514 callerInfo.name = cequintCallerIdContact.name;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700515 callerInfo.contactExists = 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;
520 callerInfo.contactExists = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700521 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700522 if (callerInfo.contactDisplayPhotoUri == null && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700523 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700524 callerInfo.contactExists = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700525 }
526 }
527
Eric Erfanianccca3152017-02-22 16:32:36 -0800528 /**
529 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
530 * when image is loaded in worker thread.
531 */
532 @WorkerThread
533 @Override
534 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
535 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700536 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
537 final String callId = myCookie.mCallId;
538 final int queryId = myCookie.mQueryId;
539 if (!isWaitingForThisQuery(callId, queryId)) {
540 return;
541 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 loadImage(photo, photoIcon, cookie);
543 }
544
545 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700546 Log.d(TAG, "Image load complete with context: ", mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800547 // TODO: may be nice to update the image view again once the newer one
548 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700549 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
550 final String callId = myCookie.mCallId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 ContactCacheEntry entry = mInfoMap.get(callId);
552
553 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700554 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800555 clearCallbacks(callId);
556 return;
557 }
558
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700559 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800560
561 // Conference call icons are being handled in CallCardPresenter.
562 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700563 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800564 entry.photo = photo;
565 entry.photoType = ContactPhotoType.CONTACT;
566 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700567 Log.v(TAG, "photo icon: ", photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
569 entry.photoType = ContactPhotoType.CONTACT;
570 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700571 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800572 entry.photo = null;
573 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
574 }
575 }
576
577 /**
578 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
579 * call state is reflected after the image is loaded.
580 */
581 @MainThread
582 @Override
583 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
584 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700585 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
586 final String callId = myCookie.mCallId;
587 final int queryId = myCookie.mQueryId;
588 if (!isWaitingForThisQuery(callId, queryId)) {
589 return;
590 }
591 sendImageNotifications(callId, mInfoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800592
593 clearCallbacks(callId);
594 }
595
596 /** Blows away the stored cache values. */
597 public void clearCache() {
598 mInfoMap.clear();
599 mCallBacks.clear();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700600 mQueryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800601 }
602
603 private ContactCacheEntry buildEntry(
604 Context context, CallerInfo info, int presentation, boolean isIncoming) {
605 final ContactCacheEntry cce = new ContactCacheEntry();
606 populateCacheEntry(context, info, cce, presentation, isIncoming);
607
608 // This will only be true for emergency numbers
609 if (info.photoResource != 0) {
610 cce.photo = context.getResources().getDrawable(info.photoResource);
611 } else if (info.isCachedPhotoCurrent) {
612 if (info.cachedPhoto != null) {
613 cce.photo = info.cachedPhoto;
614 cce.photoType = ContactPhotoType.CONTACT;
615 } else {
616 cce.photo = getDefaultContactPhotoDrawable();
617 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
618 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800619 } else {
620 cce.displayPhotoUri = info.contactDisplayPhotoUri;
621 cce.photo = null;
622 }
623
624 // Support any contact id in N because QuickContacts in N starts supporting enterprise
625 // contact id
626 if (info.lookupKeyOrNull != null
627 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
628 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
629 } else {
630 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
631 cce.lookupUri = null;
632 }
633
634 cce.lookupKey = info.lookupKeyOrNull;
635 cce.contactRingtoneUri = info.contactRingtoneUri;
636 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
637 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
638 }
639
640 return cce;
641 }
642
643 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700644 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800645 private void sendInfoNotifications(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) {
649 for (ContactInfoCacheCallback callBack : callBacks) {
650 callBack.onContactInfoComplete(callId, entry);
651 }
652 }
653 }
654
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700655 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800656 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700657 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800658 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
659 if (callBacks != null && entry.photo != null) {
660 for (ContactInfoCacheCallback callBack : callBacks) {
661 callBack.onImageLoadComplete(callId, entry);
662 }
663 }
664 }
665
666 private void clearCallbacks(String callId) {
667 mCallBacks.remove(callId);
668 }
669
670 public Drawable getDefaultContactPhotoDrawable() {
671 if (mDefaultContactPhotoDrawable == null) {
672 mDefaultContactPhotoDrawable =
673 mContext.getResources().getDrawable(R.drawable.img_no_image_automirrored);
674 }
675 return mDefaultContactPhotoDrawable;
676 }
677
678 public Drawable getConferenceDrawable() {
679 if (mConferencePhotoDrawable == null) {
680 mConferencePhotoDrawable =
681 mContext.getResources().getDrawable(R.drawable.img_conference_automirrored);
682 }
683 return mConferencePhotoDrawable;
684 }
685
686 /** Callback interface for the contact query. */
687 public interface ContactInfoCacheCallback {
688
689 void onContactInfoComplete(String callId, ContactCacheEntry entry);
690
691 void onImageLoadComplete(String callId, ContactCacheEntry entry);
692 }
693
694 /** This is cached contact info, which should be the ONLY info used by UI. */
695 public static class ContactCacheEntry {
696
697 public String namePrimary;
698 public String nameAlternative;
699 public String number;
700 public String location;
701 public String label;
702 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700703 @ContactPhotoType int photoType;
704 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800705 // Note in cache entry whether this is a pending async loading action to know whether to
706 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700707 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 /** This will be used for the "view" notification. */
709 public Uri contactUri;
710 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700711 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800712
713 public Uri lookupUri; // Sent to NotificationMananger
714 public String lookupKey;
715 public int contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
716 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700717 Uri contactRingtoneUri;
718 /** Query id to identify the query session. */
719 int queryId;
720 /** The phone number without any changes to display to the user (ex: cnap...) */
721 String originalPhoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700722 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700723
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700724 boolean isBusiness;
Eric Erfanianccca3152017-02-22 16:32:36 -0800725
726 @Override
727 public String toString() {
728 return "ContactCacheEntry{"
729 + "name='"
730 + MoreStrings.toSafeString(namePrimary)
731 + '\''
732 + ", nameAlternative='"
733 + MoreStrings.toSafeString(nameAlternative)
734 + '\''
735 + ", number='"
736 + MoreStrings.toSafeString(number)
737 + '\''
738 + ", location='"
739 + MoreStrings.toSafeString(location)
740 + '\''
741 + ", label='"
742 + label
743 + '\''
744 + ", photo="
745 + photo
746 + ", isSipCall="
747 + isSipCall
748 + ", contactUri="
749 + contactUri
750 + ", displayPhotoUri="
751 + displayPhotoUri
752 + ", contactLookupResult="
753 + contactLookupResult
754 + ", userType="
755 + userType
756 + ", contactRingtoneUri="
757 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700758 + ", queryId="
759 + queryId
760 + ", originalPhoneNumber="
761 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700762 + ", shouldShowLocation="
763 + shouldShowLocation
Eric Erfanianccca3152017-02-22 16:32:36 -0800764 + '}';
765 }
766 }
767
768 private static final class DialerCallCookieWrapper {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700769 final String callId;
770 final int numberPresentation;
771 final String cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800772
Eric Erfaniand8046e52017-04-06 09:41:50 -0700773 DialerCallCookieWrapper(String callId, int numberPresentation, String cnapName) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 this.callId = callId;
775 this.numberPresentation = numberPresentation;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700776 this.cnapName = cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800777 }
778 }
779
780 private class FindInfoCallback implements OnQueryCompleteListener {
781
782 private final boolean mIsIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700783 private final CallerInfoQueryToken mQueryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800784
Eric Erfaniand8046e52017-04-06 09:41:50 -0700785 FindInfoCallback(boolean isIncoming, CallerInfoQueryToken queryToken) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800786 mIsIncoming = isIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700787 mQueryToken = queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800788 }
789
790 @Override
791 public void onDataLoaded(int token, Object cookie, CallerInfo ci) {
792 Assert.isWorkerThread();
793 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700794 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
795 return;
796 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700797 long start = SystemClock.uptimeMillis();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700798 maybeUpdateFromCequintCallerId(ci, cw.cnapName, mIsIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700799 long time = SystemClock.uptimeMillis() - start;
800 Log.d(TAG, "Cequint Caller Id look up takes " + time + " ms.");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700801 updateCallerInfoInCacheOnAnyThread(
802 cw.callId, cw.numberPresentation, ci, mIsIncoming, true, mQueryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800803 }
804
805 @Override
806 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
807 Assert.isMainThread();
808 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
809 String callId = cw.callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700810 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
811 return;
812 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800813 ContactCacheEntry cacheEntry = mInfoMap.get(callId);
814 // This may happen only when InCallPresenter attempt to cleanup.
815 if (cacheEntry == null) {
816 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
817 clearCallbacks(callId);
818 return;
819 }
820 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700821 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800822 if (callerInfo.contactExists) {
823 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
824 } else {
825 Log.d(
826 TAG,
827 "Contact lookup done. Local contact not found and"
828 + " no remote lookup service available.");
829 }
830 clearCallbacks(callId);
831 }
832 }
833 }
834
835 class PhoneNumberServiceListener
836 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
837
838 private final String mCallId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700839 private final int mQueryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800840
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700841 PhoneNumberServiceListener(String callId, int queryId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800842 mCallId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700843 mQueryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800844 }
845
846 @Override
847 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700848 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
849 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
850 return;
851 }
852
Eric Erfanianccca3152017-02-22 16:32:36 -0800853 // If we got a miss, this is the end of the lookup pipeline,
854 // so clear the callbacks and return.
855 if (info == null) {
856 Log.d(TAG, "Contact lookup done. Remote contact not found.");
857 clearCallbacks(mCallId);
858 return;
859 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800860 ContactCacheEntry entry = new ContactCacheEntry();
861 entry.namePrimary = info.getDisplayName();
862 entry.number = info.getNumber();
863 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700864 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800865 final int type = info.getPhoneType();
866 final String label = info.getPhoneLabel();
867 if (type == Phone.TYPE_CUSTOM) {
868 entry.label = label;
869 } else {
870 final CharSequence typeStr = Phone.getTypeLabel(mContext.getResources(), type, label);
871 entry.label = typeStr == null ? null : typeStr.toString();
872 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700873 final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
874 if (oldEntry != null) {
875 // Location is only obtained from local lookup so persist
876 // the value for remote lookups. Once we have a name this
877 // field is no longer used; it is persisted here in case
878 // the UI is ever changed to use it.
879 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700880 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700881 // Contact specific ringtone is obtained from local lookup.
882 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800883 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700884
885 // If no image and it's a business, switch to using the default business avatar.
886 if (info.getImageUrl() == null && info.isBusiness()) {
887 Log.d(TAG, "Business has no image. Using default.");
888 entry.photo = mContext.getResources().getDrawable(R.drawable.img_business);
889 entry.photoType = ContactPhotoType.BUSINESS;
890 }
891
892 Log.d(TAG, "put entry into map: " + entry);
893 mInfoMap.put(mCallId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800894 sendInfoNotifications(mCallId, entry);
895
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700896 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800897
898 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700899 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800900 // We're done, so clear callbacks
901 clearCallbacks(mCallId);
902 }
903 }
904
905 @Override
906 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700907 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
908 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
909 return;
910 }
911 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryIdOfRemoteLookup, mCallId);
912 loadImage(null, bitmap, queryToken);
913 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
914 }
915 }
916
917 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
918 if (call == null || call.isConferenceCall()) {
919 return false;
920 }
921
922 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
923 if (cacheEntry == null) {
924 // No info in the map yet so it is the 1st query
925 Log.d(TAG, "needForceQuery: first query");
926 return true;
927 }
928 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
929
930 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
931 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
932 return true;
933 }
934
935 return false;
936 }
937
938 private static final class CallerInfoQueryToken {
939 final int mQueryId;
940 final String mCallId;
941
942 CallerInfoQueryToken(int queryId, String callId) {
943 mQueryId = queryId;
944 mCallId = callId;
945 }
946 }
947
948 /** Check if the queryId in the cached map is the same as the one from query result. */
949 private boolean isWaitingForThisQuery(String callId, int queryId) {
950 final ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
951 if (existingCacheEntry == null) {
952 // This might happen if lookup on background thread comes back before the initial entry is
953 // created.
954 Log.d(TAG, "Cached entry is null.");
955 return true;
956 } else {
957 int waitingQueryId = existingCacheEntry.queryId;
958 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
959 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800960 }
961 }
962}