blob: d50a5c26dd7a50a7f55b409190c8a1cf80ced5f0 [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;
Eric Erfanianea7890c2017-06-19 12:40:59 -070036import android.support.v4.content.ContextCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080037import 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;
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);
Eric Erfanian8369df02017-05-03 10:27:13 -0700101 cacheInfo.setSource(ContactSource.Type.SOURCE_TYPE_CNAP, "CNAP", 0);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700102 contactInfo.name = input.cnapName;
103 contactInfo.number = input.number;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700104 try {
105 final JSONObject contactRows =
106 new JSONObject()
107 .put(
108 Phone.CONTENT_ITEM_TYPE,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700109 new JSONObject().put(Phone.NUMBER, contactInfo.number));
Eric Erfaniand8046e52017-04-06 09:41:50 -0700110 final String jsonString =
111 new JSONObject()
112 .put(Contacts.DISPLAY_NAME, contactInfo.name)
113 .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
114 .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
115 .toString();
116 cacheInfo.setLookupKey(jsonString);
117 } catch (JSONException e) {
118 Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
119 }
120 input.service.addContact(input.context.getApplicationContext(), cacheInfo);
121 return null;
122 }
123 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800124
125 private ContactInfoCache(Context context) {
126 mContext = context;
127 mPhoneNumberService = Bindings.get(context).newPhoneNumberService(context);
128 }
129
130 public static synchronized ContactInfoCache getInstance(Context mContext) {
131 if (sCache == null) {
132 sCache = new ContactInfoCache(mContext.getApplicationContext());
133 }
134 return sCache;
135 }
136
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700137 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800138 Context context, DialerCall call, boolean isIncoming) {
139 final ContactCacheEntry entry = new ContactCacheEntry();
140
141 // TODO: get rid of caller info.
142 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
Eric Erfanian8369df02017-05-03 10:27:13 -0700143 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800144 return entry;
145 }
146
147 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700148 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800149 @NonNull Context context,
150 @NonNull CallerInfo info,
151 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700152 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 Objects.requireNonNull(info);
154 String displayName = null;
155 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800156 String label = null;
157 boolean isSipCall = false;
158
159 // It appears that there is a small change in behaviour with the
160 // PhoneUtils' startGetCallerInfo whereby if we query with an
161 // empty number, we will get a valid CallerInfo object, but with
162 // fields that are all null, and the isTemporary boolean input
163 // parameter as true.
164
165 // In the past, we would see a NULL callerinfo object, but this
166 // ends up causing null pointer exceptions elsewhere down the
167 // line in other cases, so we need to make this fix instead. It
168 // appears that this was the ONLY call to PhoneUtils
169 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
170 // an unknown contact.
171
172 // Currently, info.phoneNumber may actually be a SIP address, and
173 // if so, it might sometimes include the "sip:" prefix. That
174 // prefix isn't really useful to the user, though, so strip it off
175 // if present. (For any other URI scheme, though, leave the
176 // prefix alone.)
177 // TODO: It would be cleaner for CallerInfo to explicitly support
178 // SIP addresses instead of overloading the "phoneNumber" field.
179 // Then we could remove this hack, and instead ask the CallerInfo
180 // for a "user visible" form of the SIP address.
181 String number = info.phoneNumber;
182
183 if (!TextUtils.isEmpty(number)) {
184 isSipCall = PhoneNumberHelper.isUriNumber(number);
185 if (number.startsWith("sip:")) {
186 number = number.substring(4);
187 }
188 }
189
190 if (TextUtils.isEmpty(info.name)) {
191 // No valid "name" in the CallerInfo, so fall back to
192 // something else.
193 // (Typically, we promote the phone number up to the "name" slot
194 // onscreen, and possibly display a descriptive string in the
195 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700196 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800197 // No name *or* number! Display a generic "unknown" string
198 // (or potentially some other default based on the presentation.)
199 displayName = getPresentationString(context, presentation, info.callSubject);
200 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
201 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
202 // This case should never happen since the network should never send a phone #
203 // AND a restricted presentation. However we leave it here in case of weird
204 // network behavior
205 displayName = getPresentationString(context, presentation, info.callSubject);
206 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
207 } else if (!TextUtils.isEmpty(info.cnapName)) {
208 // No name, but we do have a valid CNAP name, so use that.
209 displayName = info.cnapName;
210 info.name = info.cnapName;
211 displayNumber = PhoneNumberHelper.formatNumber(number, context);
212 Log.d(
213 TAG,
214 " ==> cnapName available: displayName '"
215 + displayName
216 + "', displayNumber '"
217 + displayNumber
218 + "'");
219 } else {
220 // No name; all we have is a number. This is the typical
221 // case when an incoming call doesn't match any contact,
222 // or if you manually dial an outgoing number using the
223 // dialpad.
224 displayNumber = PhoneNumberHelper.formatNumber(number, context);
225
Eric Erfanianccca3152017-02-22 16:32:36 -0800226 Log.d(
227 TAG,
228 " ==> no name; falling back to number:"
229 + " displayNumber '"
230 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800231 + "'");
232 }
233 } else {
234 // We do have a valid "name" in the CallerInfo. Display that
235 // in the "name" slot, and the phone number in the "number" slot.
236 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
237 // This case should never happen since the network should never send a name
238 // AND a restricted presentation. However we leave it here in case of weird
239 // network behavior
240 displayName = getPresentationString(context, presentation, info.callSubject);
241 Log.d(
242 TAG,
243 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
244 } else {
245 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
246 // later determine whether to use the name or nameAlternative when presenting
247 displayName = info.name;
248 cce.nameAlternative = info.nameAlternative;
249 displayNumber = PhoneNumberHelper.formatNumber(number, context);
250 label = info.phoneLabel;
251 Log.d(
252 TAG,
253 " ==> name is present in CallerInfo: displayName '"
254 + displayName
255 + "', displayNumber '"
256 + displayNumber
257 + "'");
258 }
259 }
260
261 cce.namePrimary = displayName;
262 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700263 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800264 cce.label = label;
265 cce.isSipCall = isSipCall;
266 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700267 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700268 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanianff2ad7f2017-07-27 10:45:54 -0700269 cce.isEmergencyNumber = info.isEmergencyNumber();
270 cce.isVoicemailNumber = info.isVoiceMailNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800271
272 if (info.contactExists) {
273 cce.contactLookupResult = ContactLookupResult.Type.LOCAL_CONTACT;
274 }
275 }
276
277 /** Gets name strings based on some special presentation modes and the associated custom label. */
278 private static String getPresentationString(
279 Context context, int presentation, String customLabel) {
280 String name = context.getString(R.string.unknown);
281 if (!TextUtils.isEmpty(customLabel)
282 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
283 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
284 name = customLabel;
285 return name;
286 } else {
287 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
288 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context).toString();
289 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
290 name = context.getString(R.string.payphone);
291 }
292 }
293 return name;
294 }
295
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700296 ContactCacheEntry getInfo(String callId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800297 return mInfoMap.get(callId);
298 }
299
Eric Erfaniand8046e52017-04-06 09:41:50 -0700300 private static final class CnapInformationWrapper {
301 final String number;
302 final String cnapName;
303 final Context context;
304 final CachedNumberLookupService service;
305
306 CnapInformationWrapper(
307 String number, String cnapName, Context context, CachedNumberLookupService service) {
308 this.number = number;
309 this.cnapName = cnapName;
310 this.context = context;
311 this.service = service;
312 }
313 }
314
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700315 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800316 Context context, final DialerCall call, final CallerInfo info) {
317 final CachedNumberLookupService cachedNumberLookupService =
318 PhoneNumberCache.get(context).getCachedNumberLookupService();
319 if (!UserManagerCompat.isUserUnlocked(context)) {
320 Log.i(TAG, "User locked, not inserting cnap info into cache");
321 return;
322 }
323 if (cachedNumberLookupService == null
324 || TextUtils.isEmpty(info.cnapName)
325 || mInfoMap.get(call.getId()) != null) {
326 return;
327 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800328 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700329
330 cachedNumberLookupExecutor.executeParallel(
331 new CnapInformationWrapper(
332 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800333 }
334
335 /**
336 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
337 * If callback is null, no response is made, however the query is still performed and cached.
338 *
339 * @param callback The function to call back when the call is found. Can be null.
340 */
341 @MainThread
342 public void findInfo(
343 @NonNull final DialerCall call,
344 final boolean isIncoming,
345 @NonNull ContactInfoCacheCallback callback) {
346 Assert.isMainThread();
347 Objects.requireNonNull(callback);
348
349 final String callId = call.getId();
350 final ContactCacheEntry cacheEntry = mInfoMap.get(callId);
351 Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
352
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700353 // We need to force a new query if phone number has changed.
354 boolean forceQuery = needForceQuery(call, cacheEntry);
355 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
356
357 // If we have a previously obtained intermediate result return that now except needs
358 // force query.
359 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800360 Log.d(
361 TAG,
362 "Contact lookup. In memory cache hit; lookup "
363 + (callBacks == null ? "complete" : "still running"));
364 callback.onContactInfoComplete(callId, cacheEntry);
365 // If no other callbacks are in flight, we're done.
366 if (callBacks == null) {
367 return;
368 }
369 }
370
371 // If the entry already exists, add callback
372 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700373 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800374 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700375 if (!forceQuery) {
376 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
377 return;
378 }
379 } else {
380 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
381 // New lookup
382 callBacks = new ArraySet<>();
383 callBacks.add(callback);
384 mCallBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800385 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800386
387 /**
388 * Performs a query for caller information. Save any immediate data we get from the query. An
389 * asynchronous query may also be made for any data that we do not already have. Some queries,
390 * such as those for voicemail and emergency call information, will not perform an additional
391 * asynchronous query.
392 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700393 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryId, callId);
394 mQueryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800395 final CallerInfo callerInfo =
396 CallerInfoUtils.getCallerInfoForCall(
397 mContext,
398 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700399 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700400 new FindInfoCallback(isIncoming, queryToken));
Eric Erfanianccca3152017-02-22 16:32:36 -0800401
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700402 if (cacheEntry != null) {
403 // We should not override the old cache item until the new query is
404 // back. We should only update the queryId. Otherwise, we may see
405 // flicker of the name and image (old cache -> new cache before query
406 // -> new cache after query)
407 cacheEntry.queryId = queryToken.mQueryId;
408 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
409 } else {
410 ContactCacheEntry initialCacheEntry =
411 updateCallerInfoInCacheOnAnyThread(
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700412 callId, call.getNumberPresentation(), callerInfo, false, queryToken);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700413 sendInfoNotifications(callId, initialCacheEntry);
414 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800415 }
416
417 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700418 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 String callId,
420 int numberPresentation,
421 CallerInfo callerInfo,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700422 boolean didLocalLookup,
423 CallerInfoQueryToken queryToken) {
424 Log.d(
425 TAG,
426 "updateCallerInfoInCacheOnAnyThread: callId = "
427 + callId
428 + "; queryId = "
429 + queryToken.mQueryId
430 + "; didLocalLookup = "
431 + didLocalLookup);
432
Eric Erfanianff2ad7f2017-07-27 10:45:54 -0700433 ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
434 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
435
436 // Mark it as emergency/voicemail if the cache exists and was emergency/voicemail before the
437 // number changed.
438 if (existingCacheEntry != null) {
439 if (existingCacheEntry.isEmergencyNumber) {
440 callerInfo.markAsEmergency(mContext);
441 } else if (existingCacheEntry.isVoicemailNumber) {
442 callerInfo.markAsVoiceMail(mContext);
443 }
444 }
445
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 int presentationMode = numberPresentation;
447 if (callerInfo.contactExists
448 || callerInfo.isEmergencyNumber()
449 || callerInfo.isVoiceMailNumber()) {
450 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
451 }
452
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700453 // We always replace the entry. The only exception is the same photo case.
Eric Erfanian8369df02017-05-03 10:27:13 -0700454 ContactCacheEntry cacheEntry = buildEntry(mContext, callerInfo, presentationMode);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700455 cacheEntry.queryId = queryToken.mQueryId;
456
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700457 if (didLocalLookup) {
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700458 if (cacheEntry.displayPhotoUri != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700459 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
460 // we will still trigger force query so that the number can be updated on
461 // the calling screen. We need not query the image again if the previous
462 // query already has the image to avoid flickering.
463 if (existingCacheEntry != null
464 && existingCacheEntry.displayPhotoUri != null
465 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
466 && existingCacheEntry.photo != null) {
467 Log.d(TAG, "Same picture. Do not need start image load.");
468 cacheEntry.photo = existingCacheEntry.photo;
469 cacheEntry.photoType = existingCacheEntry.photoType;
470 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800471 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700472
473 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
474 // Load the image with a callback to update the image state.
475 // When the load is finished, onImageLoadComplete() will be called.
476 cacheEntry.hasPendingQuery = true;
477 ContactsAsyncHelper.startObtainPhotoAsync(
478 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
479 mContext,
480 cacheEntry.displayPhotoUri,
481 ContactInfoCache.this,
482 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800483 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700484 Log.d(TAG, "put entry into map: " + cacheEntry);
485 mInfoMap.put(callId, cacheEntry);
486 } else {
487 // Don't overwrite if there is existing cache.
488 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
489 mInfoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800490 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700491 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800492 }
493
Eric Erfaniand8046e52017-04-06 09:41:50 -0700494 private void maybeUpdateFromCequintCallerId(
495 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700496 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
497 return;
498 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700499 if (callerInfo.phoneNumber == null) {
500 return;
501 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700502 CequintCallerIdContact cequintCallerIdContact =
503 CequintCallerIdManager.getCequintCallerIdContactForInCall(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700504 mContext, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700505
Eric Erfaniand8046e52017-04-06 09:41:50 -0700506 if (cequintCallerIdContact == null) {
507 return;
508 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700509 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700510
511 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700512 callerInfo.name = cequintCallerIdContact.name;
Eric Erfanian8369df02017-05-03 10:27:13 -0700513 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700514 }
515 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
516 callerInfo.geoDescription = cequintCallerIdContact.geoDescription;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700517 callerInfo.shouldShowGeoDescription = true;
Eric Erfanian8369df02017-05-03 10:27:13 -0700518 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700519 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700520 // Don't overwrite photo in local contacts.
521 if (!callerInfo.contactExists
522 && callerInfo.contactDisplayPhotoUri == null
523 && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700524 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfanian8369df02017-05-03 10:27:13 -0700525 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700526 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700527 // Set contact to exist to avoid phone number service lookup.
528 callerInfo.contactExists = hasUpdate;
Eric Erfanian9779f962017-03-27 12:31:48 -0700529 }
530
Eric Erfanianccca3152017-02-22 16:32:36 -0800531 /**
532 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
533 * when image is loaded in worker thread.
534 */
535 @WorkerThread
536 @Override
537 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
538 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700539 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
540 final String callId = myCookie.mCallId;
541 final int queryId = myCookie.mQueryId;
542 if (!isWaitingForThisQuery(callId, queryId)) {
543 return;
544 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800545 loadImage(photo, photoIcon, cookie);
546 }
547
548 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700549 Log.d(TAG, "Image load complete with context: ", mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800550 // TODO: may be nice to update the image view again once the newer one
551 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700552 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
553 final String callId = myCookie.mCallId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800554 ContactCacheEntry entry = mInfoMap.get(callId);
555
556 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700557 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800558 clearCallbacks(callId);
559 return;
560 }
561
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700562 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800563
564 // Conference call icons are being handled in CallCardPresenter.
565 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700566 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800567 entry.photo = photo;
568 entry.photoType = ContactPhotoType.CONTACT;
569 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700570 Log.v(TAG, "photo icon: ", photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800571 entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
572 entry.photoType = ContactPhotoType.CONTACT;
573 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700574 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 entry.photo = null;
576 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
577 }
578 }
579
580 /**
581 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
582 * call state is reflected after the image is loaded.
583 */
584 @MainThread
585 @Override
586 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
587 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700588 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
589 final String callId = myCookie.mCallId;
590 final int queryId = myCookie.mQueryId;
591 if (!isWaitingForThisQuery(callId, queryId)) {
592 return;
593 }
594 sendImageNotifications(callId, mInfoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800595
596 clearCallbacks(callId);
597 }
598
599 /** Blows away the stored cache values. */
600 public void clearCache() {
601 mInfoMap.clear();
602 mCallBacks.clear();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700603 mQueryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800604 }
605
Eric Erfanian8369df02017-05-03 10:27:13 -0700606 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800607 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700608 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800609
610 // This will only be true for emergency numbers
611 if (info.photoResource != 0) {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700612 cce.photo = ContextCompat.getDrawable(context, info.photoResource);
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 } else if (info.isCachedPhotoCurrent) {
614 if (info.cachedPhoto != null) {
615 cce.photo = info.cachedPhoto;
616 cce.photoType = ContactPhotoType.CONTACT;
617 } else {
618 cce.photo = getDefaultContactPhotoDrawable();
619 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
620 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 } else {
622 cce.displayPhotoUri = info.contactDisplayPhotoUri;
623 cce.photo = null;
624 }
625
626 // Support any contact id in N because QuickContacts in N starts supporting enterprise
627 // contact id
628 if (info.lookupKeyOrNull != null
629 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
630 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
631 } else {
632 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
633 cce.lookupUri = null;
634 }
635
636 cce.lookupKey = info.lookupKeyOrNull;
637 cce.contactRingtoneUri = info.contactRingtoneUri;
638 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
639 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
640 }
641
642 return cce;
643 }
644
645 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700646 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800647 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700648 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800649 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
650 if (callBacks != null) {
651 for (ContactInfoCacheCallback callBack : callBacks) {
652 callBack.onContactInfoComplete(callId, entry);
653 }
654 }
655 }
656
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700657 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800658 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700659 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800660 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
661 if (callBacks != null && entry.photo != null) {
662 for (ContactInfoCacheCallback callBack : callBacks) {
663 callBack.onImageLoadComplete(callId, entry);
664 }
665 }
666 }
667
668 private void clearCallbacks(String callId) {
669 mCallBacks.remove(callId);
670 }
671
672 public Drawable getDefaultContactPhotoDrawable() {
673 if (mDefaultContactPhotoDrawable == null) {
674 mDefaultContactPhotoDrawable =
675 mContext.getResources().getDrawable(R.drawable.img_no_image_automirrored);
676 }
677 return mDefaultContactPhotoDrawable;
678 }
679
Eric Erfanianccca3152017-02-22 16:32:36 -0800680 /** Callback interface for the contact query. */
681 public interface ContactInfoCacheCallback {
682
683 void onContactInfoComplete(String callId, ContactCacheEntry entry);
684
685 void onImageLoadComplete(String callId, ContactCacheEntry entry);
686 }
687
688 /** This is cached contact info, which should be the ONLY info used by UI. */
689 public static class ContactCacheEntry {
690
691 public String namePrimary;
692 public String nameAlternative;
693 public String number;
694 public String location;
695 public String label;
696 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700697 @ContactPhotoType int photoType;
698 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800699 // Note in cache entry whether this is a pending async loading action to know whether to
700 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700701 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800702 /** This will be used for the "view" notification. */
703 public Uri contactUri;
704 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700705 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800706
707 public Uri lookupUri; // Sent to NotificationMananger
708 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700709 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800710 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700711 Uri contactRingtoneUri;
712 /** Query id to identify the query session. */
713 int queryId;
714 /** The phone number without any changes to display to the user (ex: cnap...) */
715 String originalPhoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700716 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700717
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700718 boolean isBusiness;
Eric Erfanianff2ad7f2017-07-27 10:45:54 -0700719 boolean isEmergencyNumber;
720 boolean isVoicemailNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800721
722 @Override
723 public String toString() {
724 return "ContactCacheEntry{"
725 + "name='"
726 + MoreStrings.toSafeString(namePrimary)
727 + '\''
728 + ", nameAlternative='"
729 + MoreStrings.toSafeString(nameAlternative)
730 + '\''
731 + ", number='"
732 + MoreStrings.toSafeString(number)
733 + '\''
734 + ", location='"
735 + MoreStrings.toSafeString(location)
736 + '\''
737 + ", label='"
738 + label
739 + '\''
740 + ", photo="
741 + photo
742 + ", isSipCall="
743 + isSipCall
744 + ", contactUri="
745 + contactUri
746 + ", displayPhotoUri="
747 + displayPhotoUri
748 + ", contactLookupResult="
749 + contactLookupResult
750 + ", userType="
751 + userType
752 + ", contactRingtoneUri="
753 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700754 + ", queryId="
755 + queryId
756 + ", originalPhoneNumber="
757 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700758 + ", shouldShowLocation="
759 + shouldShowLocation
Eric Erfanianff2ad7f2017-07-27 10:45:54 -0700760 + ", isEmergencyNumber="
761 + isEmergencyNumber
762 + ", isVoicemailNumber="
763 + isVoicemailNumber
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 Erfanian91ce7d22017-06-05 13:35:02 -0700801 updateCallerInfoInCacheOnAnyThread(cw.callId, cw.numberPresentation, ci, true, mQueryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800802 }
803
804 @Override
805 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
806 Assert.isMainThread();
807 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
808 String callId = cw.callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700809 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
810 return;
811 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800812 ContactCacheEntry cacheEntry = mInfoMap.get(callId);
813 // This may happen only when InCallPresenter attempt to cleanup.
814 if (cacheEntry == null) {
815 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
816 clearCallbacks(callId);
817 return;
818 }
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700819 // Before issuing a request for more data from other services, we only check that the
820 // contact wasn't found in the local DB. We don't check the if the cache entry already
821 // has a name because we allow overriding cnap data with data from other services.
822 if (!callerInfo.contactExists && mPhoneNumberService != null) {
823 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
824 final PhoneNumberServiceListener listener =
825 new PhoneNumberServiceListener(callId, mQueryToken.mQueryId);
826 cacheEntry.hasPendingQuery = true;
827 mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, mIsIncoming);
828 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800829 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700830 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800831 if (callerInfo.contactExists) {
832 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
833 } else {
834 Log.d(
835 TAG,
836 "Contact lookup done. Local contact not found and"
837 + " no remote lookup service available.");
838 }
839 clearCallbacks(callId);
840 }
841 }
842 }
843
844 class PhoneNumberServiceListener
845 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
846
847 private final String mCallId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700848 private final int mQueryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800849
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700850 PhoneNumberServiceListener(String callId, int queryId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800851 mCallId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700852 mQueryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800853 }
854
855 @Override
856 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700857 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
858 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
859 return;
860 }
861
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 // If we got a miss, this is the end of the lookup pipeline,
863 // so clear the callbacks and return.
864 if (info == null) {
865 Log.d(TAG, "Contact lookup done. Remote contact not found.");
866 clearCallbacks(mCallId);
867 return;
868 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800869 ContactCacheEntry entry = new ContactCacheEntry();
870 entry.namePrimary = info.getDisplayName();
871 entry.number = info.getNumber();
872 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700873 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800874 final int type = info.getPhoneType();
875 final String label = info.getPhoneLabel();
876 if (type == Phone.TYPE_CUSTOM) {
877 entry.label = label;
878 } else {
879 final CharSequence typeStr = Phone.getTypeLabel(mContext.getResources(), type, label);
880 entry.label = typeStr == null ? null : typeStr.toString();
881 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700882 final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
883 if (oldEntry != null) {
884 // Location is only obtained from local lookup so persist
885 // the value for remote lookups. Once we have a name this
886 // field is no longer used; it is persisted here in case
887 // the UI is ever changed to use it.
888 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700889 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700890 // Contact specific ringtone is obtained from local lookup.
891 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700892 entry.originalPhoneNumber = oldEntry.originalPhoneNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800893 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700894
895 // If no image and it's a business, switch to using the default business avatar.
896 if (info.getImageUrl() == null && info.isBusiness()) {
897 Log.d(TAG, "Business has no image. Using default.");
898 entry.photo = mContext.getResources().getDrawable(R.drawable.img_business);
899 entry.photoType = ContactPhotoType.BUSINESS;
900 }
901
902 Log.d(TAG, "put entry into map: " + entry);
903 mInfoMap.put(mCallId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 sendInfoNotifications(mCallId, entry);
905
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700906 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800907
908 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700909 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800910 // We're done, so clear callbacks
911 clearCallbacks(mCallId);
912 }
913 }
914
915 @Override
916 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700917 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
918 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
919 return;
920 }
921 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryIdOfRemoteLookup, mCallId);
922 loadImage(null, bitmap, queryToken);
923 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
924 }
925 }
926
927 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
928 if (call == null || call.isConferenceCall()) {
929 return false;
930 }
931
932 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
933 if (cacheEntry == null) {
934 // No info in the map yet so it is the 1st query
935 Log.d(TAG, "needForceQuery: first query");
936 return true;
937 }
938 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
939
940 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
941 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
942 return true;
943 }
944
945 return false;
946 }
947
948 private static final class CallerInfoQueryToken {
949 final int mQueryId;
950 final String mCallId;
951
952 CallerInfoQueryToken(int queryId, String callId) {
953 mQueryId = queryId;
954 mCallId = callId;
955 }
956 }
957
958 /** Check if the queryId in the cached map is the same as the one from query result. */
959 private boolean isWaitingForThisQuery(String callId, int queryId) {
960 final ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
961 if (existingCacheEntry == null) {
962 // This might happen if lookup on background thread comes back before the initial entry is
963 // created.
964 Log.d(TAG, "Cached entry is null.");
965 return true;
966 } else {
967 int waitingQueryId = existingCacheEntry.queryId;
968 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
969 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800970 }
971 }
972}