blob: 2a93945264ee1b583e36b5cc85dc154ca302cdf8 [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.incallui;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.drawable.BitmapDrawable;
22import android.graphics.drawable.Drawable;
23import android.media.RingtoneManager;
24import android.net.Uri;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.os.Build.VERSION;
26import android.os.Build.VERSION_CODES;
Eric Erfanian9779f962017-03-27 12:31:48 -070027import android.os.SystemClock;
wangqicf61ca02017-08-31 15:32:55 -070028import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080029import android.provider.ContactsContract.CommonDataKinds.Phone;
30import android.provider.ContactsContract.Contacts;
31import android.provider.ContactsContract.DisplayNameSources;
32import android.support.annotation.AnyThread;
33import android.support.annotation.MainThread;
34import android.support.annotation.NonNull;
Eric Erfaniand8046e52017-04-06 09:41:50 -070035import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080036import android.support.annotation.WorkerThread;
Eric Erfanian2ca43182017-08-31 06:57:16 -070037import android.support.v4.content.ContextCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080038import android.support.v4.os.UserManagerCompat;
39import android.telecom.TelecomManager;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070040import android.telephony.PhoneNumberUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.text.TextUtils;
42import android.util.ArrayMap;
43import android.util.ArraySet;
44import com.android.contacts.common.ContactsUtils;
45import com.android.dialer.common.Assert;
Eric Erfaniand8046e52017-04-06 09:41:50 -070046import com.android.dialer.common.concurrent.DialerExecutor;
47import com.android.dialer.common.concurrent.DialerExecutor.Worker;
48import com.android.dialer.common.concurrent.DialerExecutors;
Eric Erfanian8369df02017-05-03 10:27:13 -070049import com.android.dialer.logging.ContactLookupResult;
50import com.android.dialer.logging.ContactSource;
Eric Erfanian9779f962017-03-27 12:31:48 -070051import com.android.dialer.oem.CequintCallerIdManager;
52import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.dialer.phonenumbercache.CachedNumberLookupService;
54import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
55import com.android.dialer.phonenumbercache.ContactInfo;
56import com.android.dialer.phonenumbercache.PhoneNumberCache;
57import com.android.dialer.phonenumberutil.PhoneNumberHelper;
58import com.android.dialer.util.MoreStrings;
59import com.android.incallui.CallerInfoAsyncQuery.OnQueryCompleteListener;
60import com.android.incallui.ContactsAsyncHelper.OnImageLoadCompleteListener;
61import com.android.incallui.bindings.PhoneNumberService;
62import com.android.incallui.call.DialerCall;
63import com.android.incallui.incall.protocol.ContactPhotoType;
64import java.util.Map;
65import java.util.Objects;
66import java.util.Set;
67import java.util.concurrent.ConcurrentHashMap;
68import org.json.JSONException;
69import org.json.JSONObject;
70
71/**
72 * Class responsible for querying Contact Information for DialerCall objects. Can perform
73 * asynchronous requests to the Contact Provider for information as well as respond synchronously
74 * for any data that it currently has cached from previous queries. This class always gets called
75 * from the UI thread so it does not need thread protection.
76 */
77public class ContactInfoCache implements OnImageLoadCompleteListener {
78
79 private static final String TAG = ContactInfoCache.class.getSimpleName();
80 private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
81 private static ContactInfoCache sCache = null;
82 private final Context mContext;
83 private final PhoneNumberService mPhoneNumberService;
84 // Cache info map needs to be thread-safe since it could be modified by both main thread and
85 // worker thread.
Eric Erfaniand5e47f62017-03-15 14:41:07 -070086 private final ConcurrentHashMap<String, ContactCacheEntry> mInfoMap = new ConcurrentHashMap<>();
Eric Erfanianccca3152017-02-22 16:32:36 -080087 private final Map<String, Set<ContactInfoCacheCallback>> mCallBacks = new ArrayMap<>();
88 private Drawable mDefaultContactPhotoDrawable;
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;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700105 try {
106 final JSONObject contactRows =
107 new JSONObject()
108 .put(
109 Phone.CONTENT_ITEM_TYPE,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700110 new JSONObject().put(Phone.NUMBER, contactInfo.number));
Eric Erfaniand8046e52017-04-06 09:41:50 -0700111 final String jsonString =
112 new JSONObject()
113 .put(Contacts.DISPLAY_NAME, contactInfo.name)
114 .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
115 .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
116 .toString();
117 cacheInfo.setLookupKey(jsonString);
118 } catch (JSONException e) {
119 Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
120 }
121 input.service.addContact(input.context.getApplicationContext(), cacheInfo);
122 return null;
123 }
124 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800125
126 private ContactInfoCache(Context context) {
wangqicf61ca02017-08-31 15:32:55 -0700127 Trace.beginSection("ContactInfoCache constructor");
Eric Erfanianccca3152017-02-22 16:32:36 -0800128 mContext = context;
129 mPhoneNumberService = Bindings.get(context).newPhoneNumberService(context);
wangqicf61ca02017-08-31 15:32:55 -0700130 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800131 }
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);
Eric Erfanian8369df02017-05-03 10:27:13 -0700146 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800147 return entry;
148 }
149
150 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700151 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800152 @NonNull Context context,
153 @NonNull CallerInfo info,
154 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700155 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800156 Objects.requireNonNull(info);
157 String displayName = null;
158 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800159 String label = null;
160 boolean isSipCall = false;
161
162 // It appears that there is a small change in behaviour with the
163 // PhoneUtils' startGetCallerInfo whereby if we query with an
164 // empty number, we will get a valid CallerInfo object, but with
165 // fields that are all null, and the isTemporary boolean input
166 // parameter as true.
167
168 // In the past, we would see a NULL callerinfo object, but this
169 // ends up causing null pointer exceptions elsewhere down the
170 // line in other cases, so we need to make this fix instead. It
171 // appears that this was the ONLY call to PhoneUtils
172 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
173 // an unknown contact.
174
175 // Currently, info.phoneNumber may actually be a SIP address, and
176 // if so, it might sometimes include the "sip:" prefix. That
177 // prefix isn't really useful to the user, though, so strip it off
178 // if present. (For any other URI scheme, though, leave the
179 // prefix alone.)
180 // TODO: It would be cleaner for CallerInfo to explicitly support
181 // SIP addresses instead of overloading the "phoneNumber" field.
182 // Then we could remove this hack, and instead ask the CallerInfo
183 // for a "user visible" form of the SIP address.
184 String number = info.phoneNumber;
185
186 if (!TextUtils.isEmpty(number)) {
187 isSipCall = PhoneNumberHelper.isUriNumber(number);
188 if (number.startsWith("sip:")) {
189 number = number.substring(4);
190 }
191 }
192
193 if (TextUtils.isEmpty(info.name)) {
194 // No valid "name" in the CallerInfo, so fall back to
195 // something else.
196 // (Typically, we promote the phone number up to the "name" slot
197 // onscreen, and possibly display a descriptive string in the
198 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700199 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800200 // No name *or* number! Display a generic "unknown" string
201 // (or potentially some other default based on the presentation.)
202 displayName = getPresentationString(context, presentation, info.callSubject);
203 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
204 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
205 // This case should never happen since the network should never send a phone #
206 // AND a restricted presentation. However we leave it here in case of weird
207 // network behavior
208 displayName = getPresentationString(context, presentation, info.callSubject);
209 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
210 } else if (!TextUtils.isEmpty(info.cnapName)) {
211 // No name, but we do have a valid CNAP name, so use that.
212 displayName = info.cnapName;
213 info.name = info.cnapName;
wangqi1420a222017-09-21 09:37:40 -0700214 displayNumber = PhoneNumberHelper.formatNumber(number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800215 Log.d(
216 TAG,
217 " ==> cnapName available: displayName '"
218 + displayName
219 + "', displayNumber '"
220 + displayNumber
221 + "'");
222 } else {
223 // No name; all we have is a number. This is the typical
224 // case when an incoming call doesn't match any contact,
225 // or if you manually dial an outgoing number using the
226 // dialpad.
wangqi1420a222017-09-21 09:37:40 -0700227 displayNumber = PhoneNumberHelper.formatNumber(number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800228
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 Log.d(
230 TAG,
231 " ==> no name; falling back to number:"
232 + " displayNumber '"
233 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 + "'");
235 }
236 } else {
237 // We do have a valid "name" in the CallerInfo. Display that
238 // in the "name" slot, and the phone number in the "number" slot.
239 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
240 // This case should never happen since the network should never send a name
241 // AND a restricted presentation. However we leave it here in case of weird
242 // network behavior
243 displayName = getPresentationString(context, presentation, info.callSubject);
244 Log.d(
245 TAG,
246 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
247 } else {
248 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
249 // later determine whether to use the name or nameAlternative when presenting
250 displayName = info.name;
251 cce.nameAlternative = info.nameAlternative;
wangqi1420a222017-09-21 09:37:40 -0700252 displayNumber = PhoneNumberHelper.formatNumber(number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800253 label = info.phoneLabel;
254 Log.d(
255 TAG,
256 " ==> name is present in CallerInfo: displayName '"
257 + displayName
258 + "', displayNumber '"
259 + displayNumber
260 + "'");
261 }
262 }
263
264 cce.namePrimary = displayName;
265 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700266 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800267 cce.label = label;
268 cce.isSipCall = isSipCall;
269 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700270 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700271 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700272 cce.isEmergencyNumber = info.isEmergencyNumber();
273 cce.isVoicemailNumber = info.isVoiceMailNumber();
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) {
wangqicf61ca02017-08-31 15:32:55 -0700349 Trace.beginSection("ContactInfoCache.findInfo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800350 Assert.isMainThread();
351 Objects.requireNonNull(callback);
352
353 final String callId = call.getId();
354 final ContactCacheEntry cacheEntry = mInfoMap.get(callId);
355 Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
356
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700357 // We need to force a new query if phone number has changed.
358 boolean forceQuery = needForceQuery(call, cacheEntry);
359 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
360
361 // If we have a previously obtained intermediate result return that now except needs
362 // force query.
363 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800364 Log.d(
365 TAG,
366 "Contact lookup. In memory cache hit; lookup "
367 + (callBacks == null ? "complete" : "still running"));
368 callback.onContactInfoComplete(callId, cacheEntry);
369 // If no other callbacks are in flight, we're done.
370 if (callBacks == null) {
wangqicf61ca02017-08-31 15:32:55 -0700371 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800372 return;
373 }
374 }
375
376 // If the entry already exists, add callback
377 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700378 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800379 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700380 if (!forceQuery) {
381 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
wangqicf61ca02017-08-31 15:32:55 -0700382 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700383 return;
384 }
385 } else {
386 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
387 // New lookup
388 callBacks = new ArraySet<>();
389 callBacks.add(callback);
390 mCallBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800391 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800392
393 /**
394 * Performs a query for caller information. Save any immediate data we get from the query. An
395 * asynchronous query may also be made for any data that we do not already have. Some queries,
396 * such as those for voicemail and emergency call information, will not perform an additional
397 * asynchronous query.
398 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700399 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryId, callId);
400 mQueryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800401 final CallerInfo callerInfo =
402 CallerInfoUtils.getCallerInfoForCall(
403 mContext,
404 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700405 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700406 new FindInfoCallback(isIncoming, queryToken));
Eric Erfanianccca3152017-02-22 16:32:36 -0800407
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700408 if (cacheEntry != null) {
409 // We should not override the old cache item until the new query is
410 // back. We should only update the queryId. Otherwise, we may see
411 // flicker of the name and image (old cache -> new cache before query
412 // -> new cache after query)
413 cacheEntry.queryId = queryToken.mQueryId;
414 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
415 } else {
416 ContactCacheEntry initialCacheEntry =
417 updateCallerInfoInCacheOnAnyThread(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700418 callId, call.getNumberPresentation(), callerInfo, false, queryToken);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700419 sendInfoNotifications(callId, initialCacheEntry);
420 }
wangqicf61ca02017-08-31 15:32:55 -0700421 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800422 }
423
424 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700425 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800426 String callId,
427 int numberPresentation,
428 CallerInfo callerInfo,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700429 boolean didLocalLookup,
430 CallerInfoQueryToken queryToken) {
wangqicf61ca02017-08-31 15:32:55 -0700431 Trace.beginSection("ContactInfoCache.updateCallerInfoInCacheOnAnyThread");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700432 Log.d(
433 TAG,
434 "updateCallerInfoInCacheOnAnyThread: callId = "
435 + callId
436 + "; queryId = "
437 + queryToken.mQueryId
438 + "; didLocalLookup = "
439 + didLocalLookup);
440
Eric Erfanian2ca43182017-08-31 06:57:16 -0700441 ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
442 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
443
444 // Mark it as emergency/voicemail if the cache exists and was emergency/voicemail before the
445 // number changed.
446 if (existingCacheEntry != null) {
447 if (existingCacheEntry.isEmergencyNumber) {
448 callerInfo.markAsEmergency(mContext);
449 } else if (existingCacheEntry.isVoicemailNumber) {
450 callerInfo.markAsVoiceMail(mContext);
451 }
452 }
453
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 int presentationMode = numberPresentation;
455 if (callerInfo.contactExists
456 || callerInfo.isEmergencyNumber()
457 || callerInfo.isVoiceMailNumber()) {
458 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
459 }
460
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700461 // We always replace the entry. The only exception is the same photo case.
Eric Erfanian8369df02017-05-03 10:27:13 -0700462 ContactCacheEntry cacheEntry = buildEntry(mContext, callerInfo, presentationMode);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700463 cacheEntry.queryId = queryToken.mQueryId;
464
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700465 if (didLocalLookup) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700466 if (cacheEntry.displayPhotoUri != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700467 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
468 // we will still trigger force query so that the number can be updated on
469 // the calling screen. We need not query the image again if the previous
470 // query already has the image to avoid flickering.
471 if (existingCacheEntry != null
472 && existingCacheEntry.displayPhotoUri != null
473 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
474 && existingCacheEntry.photo != null) {
475 Log.d(TAG, "Same picture. Do not need start image load.");
476 cacheEntry.photo = existingCacheEntry.photo;
477 cacheEntry.photoType = existingCacheEntry.photoType;
478 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700480
481 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
482 // Load the image with a callback to update the image state.
483 // When the load is finished, onImageLoadComplete() will be called.
484 cacheEntry.hasPendingQuery = true;
485 ContactsAsyncHelper.startObtainPhotoAsync(
486 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
487 mContext,
488 cacheEntry.displayPhotoUri,
489 ContactInfoCache.this,
490 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800491 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700492 Log.d(TAG, "put entry into map: " + cacheEntry);
493 mInfoMap.put(callId, cacheEntry);
494 } else {
495 // Don't overwrite if there is existing cache.
496 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
497 mInfoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800498 }
wangqicf61ca02017-08-31 15:32:55 -0700499 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700500 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800501 }
502
Eric Erfaniand8046e52017-04-06 09:41:50 -0700503 private void maybeUpdateFromCequintCallerId(
504 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700505 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
506 return;
507 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700508 if (callerInfo.phoneNumber == null) {
509 return;
510 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700511 CequintCallerIdContact cequintCallerIdContact =
512 CequintCallerIdManager.getCequintCallerIdContactForInCall(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700513 mContext, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700514
Eric Erfaniand8046e52017-04-06 09:41:50 -0700515 if (cequintCallerIdContact == null) {
516 return;
517 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700518 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700519
520 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700521 callerInfo.name = cequintCallerIdContact.name;
Eric Erfanian8369df02017-05-03 10:27:13 -0700522 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700523 }
524 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
525 callerInfo.geoDescription = cequintCallerIdContact.geoDescription;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700526 callerInfo.shouldShowGeoDescription = true;
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 // Don't overwrite photo in local contacts.
530 if (!callerInfo.contactExists
531 && callerInfo.contactDisplayPhotoUri == null
532 && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700533 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfanian8369df02017-05-03 10:27:13 -0700534 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700535 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700536 // Set contact to exist to avoid phone number service lookup.
537 callerInfo.contactExists = hasUpdate;
Eric Erfanian9779f962017-03-27 12:31:48 -0700538 }
539
Eric Erfanianccca3152017-02-22 16:32:36 -0800540 /**
541 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
542 * when image is loaded in worker thread.
543 */
544 @WorkerThread
545 @Override
546 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
547 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700548 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
549 final String callId = myCookie.mCallId;
550 final int queryId = myCookie.mQueryId;
551 if (!isWaitingForThisQuery(callId, queryId)) {
552 return;
553 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800554 loadImage(photo, photoIcon, cookie);
555 }
556
557 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700558 Log.d(TAG, "Image load complete with context: ", mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800559 // TODO: may be nice to update the image view again once the newer one
560 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700561 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
562 final String callId = myCookie.mCallId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 ContactCacheEntry entry = mInfoMap.get(callId);
564
565 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700566 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800567 clearCallbacks(callId);
568 return;
569 }
570
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700571 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800572
573 // Conference call icons are being handled in CallCardPresenter.
574 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700575 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800576 entry.photo = photo;
577 entry.photoType = ContactPhotoType.CONTACT;
578 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700579 Log.v(TAG, "photo icon: ", photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
581 entry.photoType = ContactPhotoType.CONTACT;
582 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700583 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800584 entry.photo = null;
585 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
586 }
587 }
588
589 /**
590 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
591 * call state is reflected after the image is loaded.
592 */
593 @MainThread
594 @Override
595 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
596 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700597 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
598 final String callId = myCookie.mCallId;
599 final int queryId = myCookie.mQueryId;
600 if (!isWaitingForThisQuery(callId, queryId)) {
601 return;
602 }
603 sendImageNotifications(callId, mInfoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800604
605 clearCallbacks(callId);
606 }
607
608 /** Blows away the stored cache values. */
609 public void clearCache() {
610 mInfoMap.clear();
611 mCallBacks.clear();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700612 mQueryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 }
614
Eric Erfanian8369df02017-05-03 10:27:13 -0700615 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800616 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700617 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800618
619 // This will only be true for emergency numbers
620 if (info.photoResource != 0) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700621 cce.photo = ContextCompat.getDrawable(context, info.photoResource);
Eric Erfanianccca3152017-02-22 16:32:36 -0800622 } else if (info.isCachedPhotoCurrent) {
623 if (info.cachedPhoto != null) {
624 cce.photo = info.cachedPhoto;
625 cce.photoType = ContactPhotoType.CONTACT;
626 } else {
Eric Erfanianccca3152017-02-22 16:32:36 -0800627 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
628 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800629 } else {
630 cce.displayPhotoUri = info.contactDisplayPhotoUri;
631 cce.photo = null;
632 }
633
634 // Support any contact id in N because QuickContacts in N starts supporting enterprise
635 // contact id
636 if (info.lookupKeyOrNull != null
637 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
638 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
639 } else {
640 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
641 cce.lookupUri = null;
642 }
643
644 cce.lookupKey = info.lookupKeyOrNull;
645 cce.contactRingtoneUri = info.contactRingtoneUri;
646 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
647 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
648 }
649
650 return cce;
651 }
652
653 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700654 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800655 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700656 Trace.beginSection("ContactInfoCache.sendInfoNotifications");
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) {
660 for (ContactInfoCacheCallback callBack : callBacks) {
661 callBack.onContactInfoComplete(callId, entry);
662 }
663 }
wangqicf61ca02017-08-31 15:32:55 -0700664 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800665 }
666
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700667 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800668 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700669 Trace.beginSection("ContactInfoCache.sendImageNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700670 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800671 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
672 if (callBacks != null && entry.photo != null) {
673 for (ContactInfoCacheCallback callBack : callBacks) {
674 callBack.onImageLoadComplete(callId, entry);
675 }
676 }
wangqicf61ca02017-08-31 15:32:55 -0700677 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800678 }
679
680 private void clearCallbacks(String callId) {
681 mCallBacks.remove(callId);
682 }
683
Eric Erfanianccca3152017-02-22 16:32:36 -0800684 /** Callback interface for the contact query. */
685 public interface ContactInfoCacheCallback {
686
687 void onContactInfoComplete(String callId, ContactCacheEntry entry);
688
689 void onImageLoadComplete(String callId, ContactCacheEntry entry);
690 }
691
692 /** This is cached contact info, which should be the ONLY info used by UI. */
693 public static class ContactCacheEntry {
694
695 public String namePrimary;
696 public String nameAlternative;
697 public String number;
698 public String location;
699 public String label;
700 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700701 @ContactPhotoType int photoType;
702 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800703 // Note in cache entry whether this is a pending async loading action to know whether to
704 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700705 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800706 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700707 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800708
709 public Uri lookupUri; // Sent to NotificationMananger
710 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700711 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700713 Uri contactRingtoneUri;
714 /** Query id to identify the query session. */
715 int queryId;
716 /** The phone number without any changes to display to the user (ex: cnap...) */
717 String originalPhoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700718 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700719
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700720 boolean isBusiness;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700721 boolean isEmergencyNumber;
722 boolean isVoicemailNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800723
wangqiae6c8ec2017-09-28 17:39:40 -0700724 public boolean isLocalContact() {
725 return contactLookupResult == ContactLookupResult.Type.LOCAL_CONTACT;
726 }
727
Eric Erfanianccca3152017-02-22 16:32:36 -0800728 @Override
729 public String toString() {
730 return "ContactCacheEntry{"
731 + "name='"
732 + MoreStrings.toSafeString(namePrimary)
733 + '\''
734 + ", nameAlternative='"
735 + MoreStrings.toSafeString(nameAlternative)
736 + '\''
737 + ", number='"
738 + MoreStrings.toSafeString(number)
739 + '\''
740 + ", location='"
741 + MoreStrings.toSafeString(location)
742 + '\''
743 + ", label='"
744 + label
745 + '\''
746 + ", photo="
747 + photo
748 + ", isSipCall="
749 + isSipCall
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 + ", 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 Erfanian2ca43182017-08-31 06:57:16 -0700764 + ", isEmergencyNumber="
765 + isEmergencyNumber
766 + ", isVoicemailNumber="
767 + isVoicemailNumber
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 Erfanian2ca43182017-08-31 06:57:16 -0700805 updateCallerInfoInCacheOnAnyThread(cw.callId, cw.numberPresentation, ci, true, mQueryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800806 }
807
808 @Override
809 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
810 Assert.isMainThread();
811 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
812 String callId = cw.callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700813 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
814 return;
815 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800816 ContactCacheEntry cacheEntry = mInfoMap.get(callId);
817 // This may happen only when InCallPresenter attempt to cleanup.
818 if (cacheEntry == null) {
819 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
820 clearCallbacks(callId);
821 return;
822 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700823 // Before issuing a request for more data from other services, we only check that the
824 // contact wasn't found in the local DB. We don't check the if the cache entry already
825 // has a name because we allow overriding cnap data with data from other services.
826 if (!callerInfo.contactExists && mPhoneNumberService != null) {
827 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
828 final PhoneNumberServiceListener listener =
829 new PhoneNumberServiceListener(callId, mQueryToken.mQueryId);
830 cacheEntry.hasPendingQuery = true;
831 mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, mIsIncoming);
832 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800833 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700834 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800835 if (callerInfo.contactExists) {
836 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
837 } else {
838 Log.d(
839 TAG,
840 "Contact lookup done. Local contact not found and"
841 + " no remote lookup service available.");
842 }
843 clearCallbacks(callId);
844 }
845 }
846 }
847
848 class PhoneNumberServiceListener
849 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
850
851 private final String mCallId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700852 private final int mQueryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800853
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700854 PhoneNumberServiceListener(String callId, int queryId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 mCallId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700856 mQueryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800857 }
858
859 @Override
860 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700861 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
862 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
863 return;
864 }
865
Eric Erfanianccca3152017-02-22 16:32:36 -0800866 // If we got a miss, this is the end of the lookup pipeline,
867 // so clear the callbacks and return.
868 if (info == null) {
869 Log.d(TAG, "Contact lookup done. Remote contact not found.");
870 clearCallbacks(mCallId);
871 return;
872 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 ContactCacheEntry entry = new ContactCacheEntry();
874 entry.namePrimary = info.getDisplayName();
875 entry.number = info.getNumber();
876 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700877 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 final int type = info.getPhoneType();
879 final String label = info.getPhoneLabel();
880 if (type == Phone.TYPE_CUSTOM) {
881 entry.label = label;
882 } else {
883 final CharSequence typeStr = Phone.getTypeLabel(mContext.getResources(), type, label);
884 entry.label = typeStr == null ? null : typeStr.toString();
885 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700886 final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
887 if (oldEntry != null) {
888 // Location is only obtained from local lookup so persist
889 // the value for remote lookups. Once we have a name this
890 // field is no longer used; it is persisted here in case
891 // the UI is ever changed to use it.
892 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700893 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700894 // Contact specific ringtone is obtained from local lookup.
895 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700896 entry.originalPhoneNumber = oldEntry.originalPhoneNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800897 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700898
899 // If no image and it's a business, switch to using the default business avatar.
900 if (info.getImageUrl() == null && info.isBusiness()) {
901 Log.d(TAG, "Business has no image. Using default.");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700902 entry.photoType = ContactPhotoType.BUSINESS;
903 }
904
905 Log.d(TAG, "put entry into map: " + entry);
906 mInfoMap.put(mCallId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800907 sendInfoNotifications(mCallId, entry);
908
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700909 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800910
911 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700912 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800913 // We're done, so clear callbacks
914 clearCallbacks(mCallId);
915 }
916 }
917
918 @Override
919 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700920 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
921 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
922 return;
923 }
924 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryIdOfRemoteLookup, mCallId);
925 loadImage(null, bitmap, queryToken);
926 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
927 }
928 }
929
930 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
931 if (call == null || call.isConferenceCall()) {
932 return false;
933 }
934
935 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
936 if (cacheEntry == null) {
937 // No info in the map yet so it is the 1st query
938 Log.d(TAG, "needForceQuery: first query");
939 return true;
940 }
941 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
942
943 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
944 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
945 return true;
946 }
947
948 return false;
949 }
950
951 private static final class CallerInfoQueryToken {
952 final int mQueryId;
953 final String mCallId;
954
955 CallerInfoQueryToken(int queryId, String callId) {
956 mQueryId = queryId;
957 mCallId = callId;
958 }
959 }
960
961 /** Check if the queryId in the cached map is the same as the one from query result. */
962 private boolean isWaitingForThisQuery(String callId, int queryId) {
963 final ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
964 if (existingCacheEntry == null) {
965 // This might happen if lookup on background thread comes back before the initial entry is
966 // created.
967 Log.d(TAG, "Cached entry is null.");
968 return true;
969 } else {
970 int waitingQueryId = existingCacheEntry.queryId;
971 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
972 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800973 }
974 }
975}