blob: 272c2b776a0861d141d566628df1ba9721d267d3 [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<>();
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) {
wangqicf61ca02017-08-31 15:32:55 -0700126 Trace.beginSection("ContactInfoCache constructor");
Eric Erfanianccca3152017-02-22 16:32:36 -0800127 mContext = context;
128 mPhoneNumberService = Bindings.get(context).newPhoneNumberService(context);
wangqicf61ca02017-08-31 15:32:55 -0700129 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800130 }
131
132 public static synchronized ContactInfoCache getInstance(Context mContext) {
133 if (sCache == null) {
134 sCache = new ContactInfoCache(mContext.getApplicationContext());
135 }
136 return sCache;
137 }
138
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700139 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800140 Context context, DialerCall call, boolean isIncoming) {
141 final ContactCacheEntry entry = new ContactCacheEntry();
142
143 // TODO: get rid of caller info.
144 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
Eric Erfanian8369df02017-05-03 10:27:13 -0700145 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800146 return entry;
147 }
148
149 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700150 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800151 @NonNull Context context,
152 @NonNull CallerInfo info,
153 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700154 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800155 Objects.requireNonNull(info);
156 String displayName = null;
157 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800158 String label = null;
159 boolean isSipCall = false;
160
161 // It appears that there is a small change in behaviour with the
162 // PhoneUtils' startGetCallerInfo whereby if we query with an
163 // empty number, we will get a valid CallerInfo object, but with
164 // fields that are all null, and the isTemporary boolean input
165 // parameter as true.
166
167 // In the past, we would see a NULL callerinfo object, but this
168 // ends up causing null pointer exceptions elsewhere down the
169 // line in other cases, so we need to make this fix instead. It
170 // appears that this was the ONLY call to PhoneUtils
171 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
172 // an unknown contact.
173
174 // Currently, info.phoneNumber may actually be a SIP address, and
175 // if so, it might sometimes include the "sip:" prefix. That
176 // prefix isn't really useful to the user, though, so strip it off
177 // if present. (For any other URI scheme, though, leave the
178 // prefix alone.)
179 // TODO: It would be cleaner for CallerInfo to explicitly support
180 // SIP addresses instead of overloading the "phoneNumber" field.
181 // Then we could remove this hack, and instead ask the CallerInfo
182 // for a "user visible" form of the SIP address.
183 String number = info.phoneNumber;
184
185 if (!TextUtils.isEmpty(number)) {
186 isSipCall = PhoneNumberHelper.isUriNumber(number);
187 if (number.startsWith("sip:")) {
188 number = number.substring(4);
189 }
190 }
191
192 if (TextUtils.isEmpty(info.name)) {
193 // No valid "name" in the CallerInfo, so fall back to
194 // something else.
195 // (Typically, we promote the phone number up to the "name" slot
196 // onscreen, and possibly display a descriptive string in the
197 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700198 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800199 // No name *or* number! Display a generic "unknown" string
200 // (or potentially some other default based on the presentation.)
201 displayName = getPresentationString(context, presentation, info.callSubject);
202 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
203 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
204 // This case should never happen since the network should never send a phone #
205 // AND a restricted presentation. However we leave it here in case of weird
206 // network behavior
207 displayName = getPresentationString(context, presentation, info.callSubject);
208 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
209 } else if (!TextUtils.isEmpty(info.cnapName)) {
210 // No name, but we do have a valid CNAP name, so use that.
211 displayName = info.cnapName;
212 info.name = info.cnapName;
wangqi1420a222017-09-21 09:37:40 -0700213 displayNumber = PhoneNumberHelper.formatNumber(number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800214 Log.d(
215 TAG,
216 " ==> cnapName available: displayName '"
217 + displayName
218 + "', displayNumber '"
219 + displayNumber
220 + "'");
221 } else {
222 // No name; all we have is a number. This is the typical
223 // case when an incoming call doesn't match any contact,
224 // or if you manually dial an outgoing number using the
225 // dialpad.
wangqi1420a222017-09-21 09:37:40 -0700226 displayNumber = PhoneNumberHelper.formatNumber(number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800227
Eric Erfanianccca3152017-02-22 16:32:36 -0800228 Log.d(
229 TAG,
230 " ==> no name; falling back to number:"
231 + " displayNumber '"
232 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800233 + "'");
234 }
235 } else {
236 // We do have a valid "name" in the CallerInfo. Display that
237 // in the "name" slot, and the phone number in the "number" slot.
238 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
239 // This case should never happen since the network should never send a name
240 // AND a restricted presentation. However we leave it here in case of weird
241 // network behavior
242 displayName = getPresentationString(context, presentation, info.callSubject);
243 Log.d(
244 TAG,
245 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
246 } else {
247 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
248 // later determine whether to use the name or nameAlternative when presenting
249 displayName = info.name;
250 cce.nameAlternative = info.nameAlternative;
wangqi1420a222017-09-21 09:37:40 -0700251 displayNumber = PhoneNumberHelper.formatNumber(number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800252 label = info.phoneLabel;
253 Log.d(
254 TAG,
255 " ==> name is present in CallerInfo: displayName '"
256 + displayName
257 + "', displayNumber '"
258 + displayNumber
259 + "'");
260 }
261 }
262
263 cce.namePrimary = displayName;
264 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700265 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800266 cce.label = label;
267 cce.isSipCall = isSipCall;
268 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700269 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700270 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700271 cce.isEmergencyNumber = info.isEmergencyNumber();
272 cce.isVoicemailNumber = info.isVoiceMailNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800273
274 if (info.contactExists) {
275 cce.contactLookupResult = ContactLookupResult.Type.LOCAL_CONTACT;
276 }
277 }
278
279 /** Gets name strings based on some special presentation modes and the associated custom label. */
280 private static String getPresentationString(
281 Context context, int presentation, String customLabel) {
282 String name = context.getString(R.string.unknown);
283 if (!TextUtils.isEmpty(customLabel)
284 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
285 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
286 name = customLabel;
287 return name;
288 } else {
289 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
290 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context).toString();
291 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
292 name = context.getString(R.string.payphone);
293 }
294 }
295 return name;
296 }
297
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700298 ContactCacheEntry getInfo(String callId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800299 return mInfoMap.get(callId);
300 }
301
Eric Erfaniand8046e52017-04-06 09:41:50 -0700302 private static final class CnapInformationWrapper {
303 final String number;
304 final String cnapName;
305 final Context context;
306 final CachedNumberLookupService service;
307
308 CnapInformationWrapper(
309 String number, String cnapName, Context context, CachedNumberLookupService service) {
310 this.number = number;
311 this.cnapName = cnapName;
312 this.context = context;
313 this.service = service;
314 }
315 }
316
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700317 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800318 Context context, final DialerCall call, final CallerInfo info) {
319 final CachedNumberLookupService cachedNumberLookupService =
320 PhoneNumberCache.get(context).getCachedNumberLookupService();
321 if (!UserManagerCompat.isUserUnlocked(context)) {
322 Log.i(TAG, "User locked, not inserting cnap info into cache");
323 return;
324 }
325 if (cachedNumberLookupService == null
326 || TextUtils.isEmpty(info.cnapName)
327 || mInfoMap.get(call.getId()) != null) {
328 return;
329 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700331
332 cachedNumberLookupExecutor.executeParallel(
333 new CnapInformationWrapper(
334 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800335 }
336
337 /**
338 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
339 * If callback is null, no response is made, however the query is still performed and cached.
340 *
341 * @param callback The function to call back when the call is found. Can be null.
342 */
343 @MainThread
344 public void findInfo(
345 @NonNull final DialerCall call,
346 final boolean isIncoming,
347 @NonNull ContactInfoCacheCallback callback) {
wangqicf61ca02017-08-31 15:32:55 -0700348 Trace.beginSection("ContactInfoCache.findInfo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800349 Assert.isMainThread();
350 Objects.requireNonNull(callback);
351
wangqic8cf79e2017-10-17 09:21:00 -0700352 Trace.beginSection("prepare callback");
Eric Erfanianccca3152017-02-22 16:32:36 -0800353 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);
wangqic8cf79e2017-10-17 09:21:00 -0700359 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700360 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
361
362 // If we have a previously obtained intermediate result return that now except needs
363 // force query.
364 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800365 Log.d(
366 TAG,
367 "Contact lookup. In memory cache hit; lookup "
368 + (callBacks == null ? "complete" : "still running"));
369 callback.onContactInfoComplete(callId, cacheEntry);
370 // If no other callbacks are in flight, we're done.
371 if (callBacks == null) {
wangqicf61ca02017-08-31 15:32:55 -0700372 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800373 return;
374 }
375 }
376
377 // If the entry already exists, add callback
378 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700379 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800380 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700381 if (!forceQuery) {
382 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
wangqicf61ca02017-08-31 15:32:55 -0700383 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700384 return;
385 }
386 } else {
387 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
388 // New lookup
389 callBacks = new ArraySet<>();
390 callBacks.add(callback);
391 mCallBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800392 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800393
wangqic8cf79e2017-10-17 09:21:00 -0700394 Trace.beginSection("prepare query");
Eric Erfanianccca3152017-02-22 16:32:36 -0800395 /**
396 * Performs a query for caller information. Save any immediate data we get from the query. An
397 * asynchronous query may also be made for any data that we do not already have. Some queries,
398 * such as those for voicemail and emergency call information, will not perform an additional
399 * asynchronous query.
400 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700401 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryId, callId);
402 mQueryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800403 final CallerInfo callerInfo =
404 CallerInfoUtils.getCallerInfoForCall(
405 mContext,
406 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700407 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700408 new FindInfoCallback(isIncoming, queryToken));
wangqic8cf79e2017-10-17 09:21:00 -0700409 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800410
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700411 if (cacheEntry != null) {
412 // We should not override the old cache item until the new query is
413 // back. We should only update the queryId. Otherwise, we may see
414 // flicker of the name and image (old cache -> new cache before query
415 // -> new cache after query)
416 cacheEntry.queryId = queryToken.mQueryId;
417 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
418 } else {
419 ContactCacheEntry initialCacheEntry =
420 updateCallerInfoInCacheOnAnyThread(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700421 callId, call.getNumberPresentation(), callerInfo, false, queryToken);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700422 sendInfoNotifications(callId, initialCacheEntry);
423 }
wangqicf61ca02017-08-31 15:32:55 -0700424 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 }
426
427 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700428 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 String callId,
430 int numberPresentation,
431 CallerInfo callerInfo,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700432 boolean didLocalLookup,
433 CallerInfoQueryToken queryToken) {
wangqicf61ca02017-08-31 15:32:55 -0700434 Trace.beginSection("ContactInfoCache.updateCallerInfoInCacheOnAnyThread");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700435 Log.d(
436 TAG,
437 "updateCallerInfoInCacheOnAnyThread: callId = "
438 + callId
439 + "; queryId = "
440 + queryToken.mQueryId
441 + "; didLocalLookup = "
442 + didLocalLookup);
443
Eric Erfanian2ca43182017-08-31 06:57:16 -0700444 ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
445 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
446
447 // Mark it as emergency/voicemail if the cache exists and was emergency/voicemail before the
448 // number changed.
449 if (existingCacheEntry != null) {
450 if (existingCacheEntry.isEmergencyNumber) {
451 callerInfo.markAsEmergency(mContext);
452 } else if (existingCacheEntry.isVoicemailNumber) {
453 callerInfo.markAsVoiceMail(mContext);
454 }
455 }
456
Eric Erfanianccca3152017-02-22 16:32:36 -0800457 int presentationMode = numberPresentation;
458 if (callerInfo.contactExists
459 || callerInfo.isEmergencyNumber()
460 || callerInfo.isVoiceMailNumber()) {
461 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
462 }
463
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700464 // We always replace the entry. The only exception is the same photo case.
Eric Erfanian8369df02017-05-03 10:27:13 -0700465 ContactCacheEntry cacheEntry = buildEntry(mContext, callerInfo, presentationMode);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700466 cacheEntry.queryId = queryToken.mQueryId;
467
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700468 if (didLocalLookup) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700469 if (cacheEntry.displayPhotoUri != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700470 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
471 // we will still trigger force query so that the number can be updated on
472 // the calling screen. We need not query the image again if the previous
473 // query already has the image to avoid flickering.
474 if (existingCacheEntry != null
475 && existingCacheEntry.displayPhotoUri != null
476 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
477 && existingCacheEntry.photo != null) {
478 Log.d(TAG, "Same picture. Do not need start image load.");
479 cacheEntry.photo = existingCacheEntry.photo;
480 cacheEntry.photoType = existingCacheEntry.photoType;
481 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800482 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700483
484 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
485 // Load the image with a callback to update the image state.
486 // When the load is finished, onImageLoadComplete() will be called.
487 cacheEntry.hasPendingQuery = true;
488 ContactsAsyncHelper.startObtainPhotoAsync(
489 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
490 mContext,
491 cacheEntry.displayPhotoUri,
492 ContactInfoCache.this,
493 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800494 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700495 Log.d(TAG, "put entry into map: " + cacheEntry);
496 mInfoMap.put(callId, cacheEntry);
497 } else {
498 // Don't overwrite if there is existing cache.
499 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
500 mInfoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800501 }
wangqicf61ca02017-08-31 15:32:55 -0700502 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700503 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800504 }
505
Eric Erfaniand8046e52017-04-06 09:41:50 -0700506 private void maybeUpdateFromCequintCallerId(
507 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700508 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
509 return;
510 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700511 if (callerInfo.phoneNumber == null) {
512 return;
513 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700514 CequintCallerIdContact cequintCallerIdContact =
515 CequintCallerIdManager.getCequintCallerIdContactForInCall(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700516 mContext, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700517
Eric Erfaniand8046e52017-04-06 09:41:50 -0700518 if (cequintCallerIdContact == null) {
519 return;
520 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700521 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700522
523 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700524 callerInfo.name = cequintCallerIdContact.name;
Eric Erfanian8369df02017-05-03 10:27:13 -0700525 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700526 }
527 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
528 callerInfo.geoDescription = cequintCallerIdContact.geoDescription;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700529 callerInfo.shouldShowGeoDescription = true;
Eric Erfanian8369df02017-05-03 10:27:13 -0700530 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700531 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700532 // Don't overwrite photo in local contacts.
533 if (!callerInfo.contactExists
534 && callerInfo.contactDisplayPhotoUri == null
535 && cequintCallerIdContact.imageUrl != null) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700536 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl);
Eric Erfanian8369df02017-05-03 10:27:13 -0700537 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700538 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700539 // Set contact to exist to avoid phone number service lookup.
540 callerInfo.contactExists = hasUpdate;
Eric Erfanian9779f962017-03-27 12:31:48 -0700541 }
542
Eric Erfanianccca3152017-02-22 16:32:36 -0800543 /**
544 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
545 * when image is loaded in worker thread.
546 */
547 @WorkerThread
548 @Override
549 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
550 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700551 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
552 final String callId = myCookie.mCallId;
553 final int queryId = myCookie.mQueryId;
554 if (!isWaitingForThisQuery(callId, queryId)) {
555 return;
556 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 loadImage(photo, photoIcon, cookie);
558 }
559
560 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700561 Log.d(TAG, "Image load complete with context: ", mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 // TODO: may be nice to update the image view again once the newer one
563 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700564 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
565 final String callId = myCookie.mCallId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800566 ContactCacheEntry entry = mInfoMap.get(callId);
567
568 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700569 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800570 clearCallbacks(callId);
571 return;
572 }
573
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700574 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800575
576 // Conference call icons are being handled in CallCardPresenter.
577 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700578 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800579 entry.photo = photo;
580 entry.photoType = ContactPhotoType.CONTACT;
581 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700582 Log.v(TAG, "photo icon: ", photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800583 entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
584 entry.photoType = ContactPhotoType.CONTACT;
585 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700586 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800587 entry.photo = null;
588 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
589 }
590 }
591
592 /**
593 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
594 * call state is reflected after the image is loaded.
595 */
596 @MainThread
597 @Override
598 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
599 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700600 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
601 final String callId = myCookie.mCallId;
602 final int queryId = myCookie.mQueryId;
603 if (!isWaitingForThisQuery(callId, queryId)) {
604 return;
605 }
606 sendImageNotifications(callId, mInfoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800607
608 clearCallbacks(callId);
609 }
610
611 /** Blows away the stored cache values. */
612 public void clearCache() {
613 mInfoMap.clear();
614 mCallBacks.clear();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700615 mQueryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800616 }
617
Eric Erfanian8369df02017-05-03 10:27:13 -0700618 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800619 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700620 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800621
622 // This will only be true for emergency numbers
623 if (info.photoResource != 0) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700624 cce.photo = ContextCompat.getDrawable(context, info.photoResource);
Eric Erfanianccca3152017-02-22 16:32:36 -0800625 } else if (info.isCachedPhotoCurrent) {
626 if (info.cachedPhoto != null) {
627 cce.photo = info.cachedPhoto;
628 cce.photoType = ContactPhotoType.CONTACT;
629 } else {
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
631 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800632 } else {
633 cce.displayPhotoUri = info.contactDisplayPhotoUri;
634 cce.photo = null;
635 }
636
637 // Support any contact id in N because QuickContacts in N starts supporting enterprise
638 // contact id
639 if (info.lookupKeyOrNull != null
640 && (VERSION.SDK_INT >= VERSION_CODES.N || info.contactIdOrZero != 0)) {
641 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
642 } else {
643 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
644 cce.lookupUri = null;
645 }
646
647 cce.lookupKey = info.lookupKeyOrNull;
648 cce.contactRingtoneUri = info.contactRingtoneUri;
649 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
650 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
651 }
652
653 return cce;
654 }
655
656 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700657 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800658 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700659 Trace.beginSection("ContactInfoCache.sendInfoNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700660 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800661 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
662 if (callBacks != null) {
663 for (ContactInfoCacheCallback callBack : callBacks) {
664 callBack.onContactInfoComplete(callId, entry);
665 }
666 }
wangqicf61ca02017-08-31 15:32:55 -0700667 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800668 }
669
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700670 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800671 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700672 Trace.beginSection("ContactInfoCache.sendImageNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700673 Assert.isMainThread();
Eric Erfanianccca3152017-02-22 16:32:36 -0800674 final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
675 if (callBacks != null && entry.photo != null) {
676 for (ContactInfoCacheCallback callBack : callBacks) {
677 callBack.onImageLoadComplete(callId, entry);
678 }
679 }
wangqicf61ca02017-08-31 15:32:55 -0700680 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800681 }
682
683 private void clearCallbacks(String callId) {
684 mCallBacks.remove(callId);
685 }
686
Eric Erfanianccca3152017-02-22 16:32:36 -0800687 /** Callback interface for the contact query. */
688 public interface ContactInfoCacheCallback {
689
690 void onContactInfoComplete(String callId, ContactCacheEntry entry);
691
692 void onImageLoadComplete(String callId, ContactCacheEntry entry);
693 }
694
695 /** This is cached contact info, which should be the ONLY info used by UI. */
696 public static class ContactCacheEntry {
697
698 public String namePrimary;
699 public String nameAlternative;
700 public String number;
701 public String location;
702 public String label;
703 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700704 @ContactPhotoType int photoType;
705 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800706 // Note in cache entry whether this is a pending async loading action to know whether to
707 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700708 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800709 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700710 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800711
712 public Uri lookupUri; // Sent to NotificationMananger
713 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700714 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700716 Uri contactRingtoneUri;
717 /** Query id to identify the query session. */
718 int queryId;
719 /** The phone number without any changes to display to the user (ex: cnap...) */
720 String originalPhoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700721 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700722
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700723 boolean isBusiness;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700724 boolean isEmergencyNumber;
725 boolean isVoicemailNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800726
wangqiae6c8ec2017-09-28 17:39:40 -0700727 public boolean isLocalContact() {
728 return contactLookupResult == ContactLookupResult.Type.LOCAL_CONTACT;
729 }
730
Eric Erfanianccca3152017-02-22 16:32:36 -0800731 @Override
732 public String toString() {
733 return "ContactCacheEntry{"
734 + "name='"
735 + MoreStrings.toSafeString(namePrimary)
736 + '\''
737 + ", nameAlternative='"
738 + MoreStrings.toSafeString(nameAlternative)
739 + '\''
740 + ", number='"
741 + MoreStrings.toSafeString(number)
742 + '\''
743 + ", location='"
744 + MoreStrings.toSafeString(location)
745 + '\''
746 + ", label='"
747 + label
748 + '\''
749 + ", photo="
750 + photo
751 + ", isSipCall="
752 + isSipCall
Eric Erfanianccca3152017-02-22 16:32:36 -0800753 + ", displayPhotoUri="
754 + displayPhotoUri
755 + ", contactLookupResult="
756 + contactLookupResult
757 + ", userType="
758 + userType
759 + ", contactRingtoneUri="
760 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700761 + ", queryId="
762 + queryId
763 + ", originalPhoneNumber="
764 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700765 + ", shouldShowLocation="
766 + shouldShowLocation
Eric Erfanian2ca43182017-08-31 06:57:16 -0700767 + ", isEmergencyNumber="
768 + isEmergencyNumber
769 + ", isVoicemailNumber="
770 + isVoicemailNumber
Eric Erfanianccca3152017-02-22 16:32:36 -0800771 + '}';
772 }
773 }
774
775 private static final class DialerCallCookieWrapper {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700776 final String callId;
777 final int numberPresentation;
778 final String cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800779
Eric Erfaniand8046e52017-04-06 09:41:50 -0700780 DialerCallCookieWrapper(String callId, int numberPresentation, String cnapName) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800781 this.callId = callId;
782 this.numberPresentation = numberPresentation;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700783 this.cnapName = cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800784 }
785 }
786
787 private class FindInfoCallback implements OnQueryCompleteListener {
788
789 private final boolean mIsIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700790 private final CallerInfoQueryToken mQueryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800791
Eric Erfaniand8046e52017-04-06 09:41:50 -0700792 FindInfoCallback(boolean isIncoming, CallerInfoQueryToken queryToken) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800793 mIsIncoming = isIncoming;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700794 mQueryToken = queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800795 }
796
797 @Override
798 public void onDataLoaded(int token, Object cookie, CallerInfo ci) {
799 Assert.isWorkerThread();
800 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700801 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
802 return;
803 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700804 long start = SystemClock.uptimeMillis();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700805 maybeUpdateFromCequintCallerId(ci, cw.cnapName, mIsIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700806 long time = SystemClock.uptimeMillis() - start;
807 Log.d(TAG, "Cequint Caller Id look up takes " + time + " ms.");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700808 updateCallerInfoInCacheOnAnyThread(cw.callId, cw.numberPresentation, ci, true, mQueryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 }
810
811 @Override
812 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
wangqi9982f0d2017-10-11 17:46:07 -0700813 Trace.beginSection("ContactInfoCache.FindInfoCallback.onQueryComplete");
Eric Erfanianccca3152017-02-22 16:32:36 -0800814 Assert.isMainThread();
815 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
816 String callId = cw.callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700817 if (!isWaitingForThisQuery(cw.callId, mQueryToken.mQueryId)) {
wangqi9982f0d2017-10-11 17:46:07 -0700818 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700819 return;
820 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 ContactCacheEntry cacheEntry = mInfoMap.get(callId);
822 // This may happen only when InCallPresenter attempt to cleanup.
823 if (cacheEntry == null) {
824 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
825 clearCallbacks(callId);
wangqi9982f0d2017-10-11 17:46:07 -0700826 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 return;
828 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700829 // Before issuing a request for more data from other services, we only check that the
830 // contact wasn't found in the local DB. We don't check the if the cache entry already
831 // has a name because we allow overriding cnap data with data from other services.
832 if (!callerInfo.contactExists && mPhoneNumberService != null) {
833 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
834 final PhoneNumberServiceListener listener =
835 new PhoneNumberServiceListener(callId, mQueryToken.mQueryId);
836 cacheEntry.hasPendingQuery = true;
837 mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, mIsIncoming);
838 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800839 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700840 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800841 if (callerInfo.contactExists) {
842 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
843 } else {
844 Log.d(
845 TAG,
846 "Contact lookup done. Local contact not found and"
847 + " no remote lookup service available.");
848 }
849 clearCallbacks(callId);
850 }
wangqi9982f0d2017-10-11 17:46:07 -0700851 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800852 }
853 }
854
855 class PhoneNumberServiceListener
856 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
857
858 private final String mCallId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700859 private final int mQueryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800860
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700861 PhoneNumberServiceListener(String callId, int queryId) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 mCallId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700863 mQueryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800864 }
865
866 @Override
867 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700868 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
869 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
870 return;
871 }
872
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 // If we got a miss, this is the end of the lookup pipeline,
874 // so clear the callbacks and return.
875 if (info == null) {
876 Log.d(TAG, "Contact lookup done. Remote contact not found.");
877 clearCallbacks(mCallId);
878 return;
879 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800880 ContactCacheEntry entry = new ContactCacheEntry();
881 entry.namePrimary = info.getDisplayName();
882 entry.number = info.getNumber();
883 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700884 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800885 final int type = info.getPhoneType();
886 final String label = info.getPhoneLabel();
887 if (type == Phone.TYPE_CUSTOM) {
888 entry.label = label;
889 } else {
890 final CharSequence typeStr = Phone.getTypeLabel(mContext.getResources(), type, label);
891 entry.label = typeStr == null ? null : typeStr.toString();
892 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700893 final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
894 if (oldEntry != null) {
895 // Location is only obtained from local lookup so persist
896 // the value for remote lookups. Once we have a name this
897 // field is no longer used; it is persisted here in case
898 // the UI is ever changed to use it.
899 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700900 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700901 // Contact specific ringtone is obtained from local lookup.
902 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700903 entry.originalPhoneNumber = oldEntry.originalPhoneNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700905
906 // If no image and it's a business, switch to using the default business avatar.
907 if (info.getImageUrl() == null && info.isBusiness()) {
908 Log.d(TAG, "Business has no image. Using default.");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700909 entry.photoType = ContactPhotoType.BUSINESS;
910 }
911
912 Log.d(TAG, "put entry into map: " + entry);
913 mInfoMap.put(mCallId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800914 sendInfoNotifications(mCallId, entry);
915
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700916 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800917
918 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700919 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800920 // We're done, so clear callbacks
921 clearCallbacks(mCallId);
922 }
923 }
924
925 @Override
926 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700927 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
928 if (!isWaitingForThisQuery(mCallId, mQueryIdOfRemoteLookup)) {
929 return;
930 }
931 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(mQueryIdOfRemoteLookup, mCallId);
932 loadImage(null, bitmap, queryToken);
933 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
934 }
935 }
936
937 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
938 if (call == null || call.isConferenceCall()) {
939 return false;
940 }
941
942 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
943 if (cacheEntry == null) {
944 // No info in the map yet so it is the 1st query
945 Log.d(TAG, "needForceQuery: first query");
946 return true;
947 }
948 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
949
950 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
951 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
952 return true;
953 }
954
955 return false;
956 }
957
958 private static final class CallerInfoQueryToken {
959 final int mQueryId;
960 final String mCallId;
961
962 CallerInfoQueryToken(int queryId, String callId) {
963 mQueryId = queryId;
964 mCallId = callId;
965 }
966 }
967
968 /** Check if the queryId in the cached map is the same as the one from query result. */
969 private boolean isWaitingForThisQuery(String callId, int queryId) {
970 final ContactCacheEntry existingCacheEntry = mInfoMap.get(callId);
971 if (existingCacheEntry == null) {
972 // This might happen if lookup on background thread comes back before the initial entry is
973 // created.
974 Log.d(TAG, "Cached entry is null.");
975 return true;
976 } else {
977 int waitingQueryId = existingCacheEntry.queryId;
978 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
979 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800980 }
981 }
982}