blob: eefd4833cd038f3b8851734139ed5ecc3e1a13e3 [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 Erfanian9779f962017-03-27 12:31:48 -070025import android.os.SystemClock;
wangqicf61ca02017-08-31 15:32:55 -070026import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080027import android.provider.ContactsContract.CommonDataKinds.Phone;
28import android.provider.ContactsContract.Contacts;
29import android.provider.ContactsContract.DisplayNameSources;
30import android.support.annotation.AnyThread;
31import android.support.annotation.MainThread;
32import android.support.annotation.NonNull;
Eric Erfaniand8046e52017-04-06 09:41:50 -070033import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080034import android.support.annotation.WorkerThread;
Eric Erfanian2ca43182017-08-31 06:57:16 -070035import android.support.v4.content.ContextCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080036import android.support.v4.os.UserManagerCompat;
37import android.telecom.TelecomManager;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070038import android.telephony.PhoneNumberUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080039import android.text.TextUtils;
40import android.util.ArrayMap;
41import android.util.ArraySet;
42import com.android.contacts.common.ContactsUtils;
43import com.android.dialer.common.Assert;
Eric Erfaniand8046e52017-04-06 09:41:50 -070044import com.android.dialer.common.concurrent.DialerExecutor;
45import com.android.dialer.common.concurrent.DialerExecutor.Worker;
zachh0cd36a62017-10-31 12:04:05 -070046import com.android.dialer.common.concurrent.DialerExecutorComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070047import com.android.dialer.logging.ContactLookupResult;
48import com.android.dialer.logging.ContactSource;
Eric Erfanian9779f962017-03-27 12:31:48 -070049import com.android.dialer.oem.CequintCallerIdManager;
50import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import com.android.dialer.phonenumbercache.CachedNumberLookupService;
52import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
53import com.android.dialer.phonenumbercache.ContactInfo;
54import com.android.dialer.phonenumbercache.PhoneNumberCache;
55import com.android.dialer.phonenumberutil.PhoneNumberHelper;
56import com.android.dialer.util.MoreStrings;
57import com.android.incallui.CallerInfoAsyncQuery.OnQueryCompleteListener;
58import com.android.incallui.ContactsAsyncHelper.OnImageLoadCompleteListener;
59import com.android.incallui.bindings.PhoneNumberService;
60import com.android.incallui.call.DialerCall;
61import com.android.incallui.incall.protocol.ContactPhotoType;
62import java.util.Map;
63import java.util.Objects;
64import java.util.Set;
65import java.util.concurrent.ConcurrentHashMap;
66import org.json.JSONException;
67import org.json.JSONObject;
68
69/**
70 * Class responsible for querying Contact Information for DialerCall objects. Can perform
71 * asynchronous requests to the Contact Provider for information as well as respond synchronously
72 * for any data that it currently has cached from previous queries. This class always gets called
73 * from the UI thread so it does not need thread protection.
74 */
75public class ContactInfoCache implements OnImageLoadCompleteListener {
76
77 private static final String TAG = ContactInfoCache.class.getSimpleName();
78 private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
linyuh183cb712017-12-27 17:02:37 -080079 private static ContactInfoCache cache = null;
80 private final Context context;
81 private final PhoneNumberService phoneNumberService;
Eric Erfanianccca3152017-02-22 16:32:36 -080082 // Cache info map needs to be thread-safe since it could be modified by both main thread and
83 // worker thread.
linyuh183cb712017-12-27 17:02:37 -080084 private final ConcurrentHashMap<String, ContactCacheEntry> infoMap = new ConcurrentHashMap<>();
85 private final Map<String, Set<ContactInfoCacheCallback>> callBacks = new ArrayMap<>();
86 private int queryId;
zachh6a4cebd2017-10-24 17:10:06 -070087 private final DialerExecutor<CnapInformationWrapper> cachedNumberLookupExecutor;
Eric Erfaniand8046e52017-04-06 09:41:50 -070088
89 private static class CachedNumberLookupWorker implements Worker<CnapInformationWrapper, Void> {
90 @Nullable
91 @Override
92 public Void doInBackground(@Nullable CnapInformationWrapper input) {
93 if (input == null) {
94 return null;
95 }
96 ContactInfo contactInfo = new ContactInfo();
97 CachedContactInfo cacheInfo = input.service.buildCachedContactInfo(contactInfo);
Eric Erfanian8369df02017-05-03 10:27:13 -070098 cacheInfo.setSource(ContactSource.Type.SOURCE_TYPE_CNAP, "CNAP", 0);
Eric Erfaniand8046e52017-04-06 09:41:50 -070099 contactInfo.name = input.cnapName;
100 contactInfo.number = input.number;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700101 try {
102 final JSONObject contactRows =
103 new JSONObject()
104 .put(
105 Phone.CONTENT_ITEM_TYPE,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700106 new JSONObject().put(Phone.NUMBER, contactInfo.number));
Eric Erfaniand8046e52017-04-06 09:41:50 -0700107 final String jsonString =
108 new JSONObject()
109 .put(Contacts.DISPLAY_NAME, contactInfo.name)
110 .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
111 .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
112 .toString();
113 cacheInfo.setLookupKey(jsonString);
114 } catch (JSONException e) {
115 Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
116 }
117 input.service.addContact(input.context.getApplicationContext(), cacheInfo);
118 return null;
119 }
120 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800121
122 private ContactInfoCache(Context context) {
wangqicf61ca02017-08-31 15:32:55 -0700123 Trace.beginSection("ContactInfoCache constructor");
linyuh183cb712017-12-27 17:02:37 -0800124 this.context = context;
125 phoneNumberService = Bindings.get(context).newPhoneNumberService(context);
zachh6a4cebd2017-10-24 17:10:06 -0700126 cachedNumberLookupExecutor =
linyuh183cb712017-12-27 17:02:37 -0800127 DialerExecutorComponent.get(this.context)
zachh0cd36a62017-10-31 12:04:05 -0700128 .dialerExecutorFactory()
129 .createNonUiTaskBuilder(new CachedNumberLookupWorker())
130 .build();
wangqicf61ca02017-08-31 15:32:55 -0700131 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800132 }
133
134 public static synchronized ContactInfoCache getInstance(Context mContext) {
linyuh183cb712017-12-27 17:02:37 -0800135 if (cache == null) {
136 cache = new ContactInfoCache(mContext.getApplicationContext());
Eric Erfanianccca3152017-02-22 16:32:36 -0800137 }
linyuh183cb712017-12-27 17:02:37 -0800138 return cache;
Eric Erfanianccca3152017-02-22 16:32:36 -0800139 }
140
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700141 static ContactCacheEntry buildCacheEntryFromCall(
Eric Erfanianccca3152017-02-22 16:32:36 -0800142 Context context, DialerCall call, boolean isIncoming) {
143 final ContactCacheEntry entry = new ContactCacheEntry();
144
145 // TODO: get rid of caller info.
146 final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
Eric Erfanian8369df02017-05-03 10:27:13 -0700147 ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation());
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 return entry;
149 }
150
151 /** Populate a cache entry from a call (which got converted into a caller info). */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700152 private static void populateCacheEntry(
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 @NonNull Context context,
154 @NonNull CallerInfo info,
155 @NonNull ContactCacheEntry cce,
Eric Erfanian8369df02017-05-03 10:27:13 -0700156 int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800157 Objects.requireNonNull(info);
158 String displayName = null;
159 String displayNumber = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800160 String label = null;
161 boolean isSipCall = false;
162
163 // It appears that there is a small change in behaviour with the
164 // PhoneUtils' startGetCallerInfo whereby if we query with an
165 // empty number, we will get a valid CallerInfo object, but with
166 // fields that are all null, and the isTemporary boolean input
167 // parameter as true.
168
169 // In the past, we would see a NULL callerinfo object, but this
170 // ends up causing null pointer exceptions elsewhere down the
171 // line in other cases, so we need to make this fix instead. It
172 // appears that this was the ONLY call to PhoneUtils
173 // .getCallerInfo() that relied on a NULL CallerInfo to indicate
174 // an unknown contact.
175
176 // Currently, info.phoneNumber may actually be a SIP address, and
177 // if so, it might sometimes include the "sip:" prefix. That
178 // prefix isn't really useful to the user, though, so strip it off
179 // if present. (For any other URI scheme, though, leave the
180 // prefix alone.)
181 // TODO: It would be cleaner for CallerInfo to explicitly support
182 // SIP addresses instead of overloading the "phoneNumber" field.
183 // Then we could remove this hack, and instead ask the CallerInfo
184 // for a "user visible" form of the SIP address.
185 String number = info.phoneNumber;
186
187 if (!TextUtils.isEmpty(number)) {
188 isSipCall = PhoneNumberHelper.isUriNumber(number);
189 if (number.startsWith("sip:")) {
190 number = number.substring(4);
191 }
192 }
193
194 if (TextUtils.isEmpty(info.name)) {
195 // No valid "name" in the CallerInfo, so fall back to
196 // something else.
197 // (Typically, we promote the phone number up to the "name" slot
198 // onscreen, and possibly display a descriptive string in the
199 // "number" slot.)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700200 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(info.cnapName)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800201 // No name *or* number! Display a generic "unknown" string
202 // (or potentially some other default based on the presentation.)
203 displayName = getPresentationString(context, presentation, info.callSubject);
204 Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
205 } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
206 // This case should never happen since the network should never send a phone #
207 // AND a restricted presentation. However we leave it here in case of weird
208 // network behavior
209 displayName = getPresentationString(context, presentation, info.callSubject);
210 Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
211 } else if (!TextUtils.isEmpty(info.cnapName)) {
212 // No name, but we do have a valid CNAP name, so use that.
213 displayName = info.cnapName;
214 info.name = info.cnapName;
linyuhb06d0092018-03-01 15:05:36 -0800215 displayNumber = PhoneNumberHelper.formatNumber(context, number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800216 Log.d(
217 TAG,
218 " ==> cnapName available: displayName '"
219 + displayName
220 + "', displayNumber '"
221 + displayNumber
222 + "'");
223 } else {
224 // No name; all we have is a number. This is the typical
225 // case when an incoming call doesn't match any contact,
226 // or if you manually dial an outgoing number using the
227 // dialpad.
linyuhb06d0092018-03-01 15:05:36 -0800228 displayNumber = PhoneNumberHelper.formatNumber(context, number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800229
Eric Erfanianccca3152017-02-22 16:32:36 -0800230 Log.d(
231 TAG,
232 " ==> no name; falling back to number:"
233 + " displayNumber '"
234 + Log.pii(displayNumber)
Eric Erfanianccca3152017-02-22 16:32:36 -0800235 + "'");
236 }
237 } else {
238 // We do have a valid "name" in the CallerInfo. Display that
239 // in the "name" slot, and the phone number in the "number" slot.
240 if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
241 // This case should never happen since the network should never send a name
242 // AND a restricted presentation. However we leave it here in case of weird
243 // network behavior
244 displayName = getPresentationString(context, presentation, info.callSubject);
245 Log.d(
246 TAG,
247 " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
248 } else {
249 // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
250 // later determine whether to use the name or nameAlternative when presenting
251 displayName = info.name;
252 cce.nameAlternative = info.nameAlternative;
linyuhb06d0092018-03-01 15:05:36 -0800253 displayNumber = PhoneNumberHelper.formatNumber(context, number, info.countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800254 label = info.phoneLabel;
255 Log.d(
256 TAG,
257 " ==> name is present in CallerInfo: displayName '"
258 + displayName
259 + "', displayNumber '"
260 + displayNumber
261 + "'");
262 }
263 }
264
265 cce.namePrimary = displayName;
266 cce.number = displayNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700267 cce.location = info.geoDescription;
Eric Erfanianccca3152017-02-22 16:32:36 -0800268 cce.label = label;
269 cce.isSipCall = isSipCall;
270 cce.userType = info.userType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700271 cce.originalPhoneNumber = info.phoneNumber;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700272 cce.shouldShowLocation = info.shouldShowGeoDescription;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700273 cce.isEmergencyNumber = info.isEmergencyNumber();
274 cce.isVoicemailNumber = info.isVoiceMailNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800275
276 if (info.contactExists) {
wangqi262b6f22018-03-16 12:27:56 -0700277 cce.contactLookupResult = info.contactLookupResultType;
Eric Erfanianccca3152017-02-22 16:32:36 -0800278 }
279 }
280
281 /** Gets name strings based on some special presentation modes and the associated custom label. */
282 private static String getPresentationString(
283 Context context, int presentation, String customLabel) {
284 String name = context.getString(R.string.unknown);
285 if (!TextUtils.isEmpty(customLabel)
286 && ((presentation == TelecomManager.PRESENTATION_UNKNOWN)
287 || (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
288 name = customLabel;
289 return name;
290 } else {
291 if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
zachh03b13192018-01-26 10:56:46 -0800292 name = PhoneNumberHelper.getDisplayNameForRestrictedNumber(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800293 } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
294 name = context.getString(R.string.payphone);
295 }
296 }
297 return name;
298 }
299
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700300 ContactCacheEntry getInfo(String callId) {
linyuh183cb712017-12-27 17:02:37 -0800301 return infoMap.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800302 }
303
Eric Erfaniand8046e52017-04-06 09:41:50 -0700304 private static final class CnapInformationWrapper {
305 final String number;
306 final String cnapName;
307 final Context context;
308 final CachedNumberLookupService service;
309
310 CnapInformationWrapper(
311 String number, String cnapName, Context context, CachedNumberLookupService service) {
312 this.number = number;
313 this.cnapName = cnapName;
314 this.context = context;
315 this.service = service;
316 }
317 }
318
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700319 void maybeInsertCnapInformationIntoCache(
Eric Erfanianccca3152017-02-22 16:32:36 -0800320 Context context, final DialerCall call, final CallerInfo info) {
321 final CachedNumberLookupService cachedNumberLookupService =
322 PhoneNumberCache.get(context).getCachedNumberLookupService();
323 if (!UserManagerCompat.isUserUnlocked(context)) {
324 Log.i(TAG, "User locked, not inserting cnap info into cache");
325 return;
326 }
327 if (cachedNumberLookupService == null
328 || TextUtils.isEmpty(info.cnapName)
linyuh183cb712017-12-27 17:02:37 -0800329 || infoMap.get(call.getId()) != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 return;
331 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800332 Log.i(TAG, "Found contact with CNAP name - inserting into cache");
Eric Erfaniand8046e52017-04-06 09:41:50 -0700333
334 cachedNumberLookupExecutor.executeParallel(
335 new CnapInformationWrapper(
336 call.getNumber(), info.cnapName, context, cachedNumberLookupService));
Eric Erfanianccca3152017-02-22 16:32:36 -0800337 }
338
339 /**
340 * Requests contact data for the DialerCall object passed in. Returns the data through callback.
341 * If callback is null, no response is made, however the query is still performed and cached.
342 *
343 * @param callback The function to call back when the call is found. Can be null.
344 */
345 @MainThread
346 public void findInfo(
347 @NonNull final DialerCall call,
348 final boolean isIncoming,
349 @NonNull ContactInfoCacheCallback callback) {
wangqicf61ca02017-08-31 15:32:55 -0700350 Trace.beginSection("ContactInfoCache.findInfo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800351 Assert.isMainThread();
352 Objects.requireNonNull(callback);
353
wangqic8cf79e2017-10-17 09:21:00 -0700354 Trace.beginSection("prepare callback");
Eric Erfanianccca3152017-02-22 16:32:36 -0800355 final String callId = call.getId();
linyuh183cb712017-12-27 17:02:37 -0800356 final ContactCacheEntry cacheEntry = infoMap.get(callId);
357 Set<ContactInfoCacheCallback> callBacks = this.callBacks.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800358
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700359 // We need to force a new query if phone number has changed.
360 boolean forceQuery = needForceQuery(call, cacheEntry);
wangqic8cf79e2017-10-17 09:21:00 -0700361 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700362 Log.d(TAG, "findInfo: callId = " + callId + "; forceQuery = " + forceQuery);
363
364 // If we have a previously obtained intermediate result return that now except needs
365 // force query.
366 if (cacheEntry != null && !forceQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800367 Log.d(
368 TAG,
369 "Contact lookup. In memory cache hit; lookup "
370 + (callBacks == null ? "complete" : "still running"));
371 callback.onContactInfoComplete(callId, cacheEntry);
372 // If no other callbacks are in flight, we're done.
373 if (callBacks == null) {
wangqicf61ca02017-08-31 15:32:55 -0700374 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800375 return;
376 }
377 }
378
379 // If the entry already exists, add callback
380 if (callBacks != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700381 Log.d(TAG, "Another query is in progress, add callback only.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800382 callBacks.add(callback);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700383 if (!forceQuery) {
384 Log.d(TAG, "No need to query again, just return and wait for existing query to finish");
wangqicf61ca02017-08-31 15:32:55 -0700385 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700386 return;
387 }
388 } else {
389 Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
390 // New lookup
391 callBacks = new ArraySet<>();
392 callBacks.add(callback);
linyuh183cb712017-12-27 17:02:37 -0800393 this.callBacks.put(callId, callBacks);
Eric Erfanianccca3152017-02-22 16:32:36 -0800394 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800395
wangqic8cf79e2017-10-17 09:21:00 -0700396 Trace.beginSection("prepare query");
Eric Erfanianccca3152017-02-22 16:32:36 -0800397 /**
398 * Performs a query for caller information. Save any immediate data we get from the query. An
399 * asynchronous query may also be made for any data that we do not already have. Some queries,
400 * such as those for voicemail and emergency call information, will not perform an additional
401 * asynchronous query.
402 */
linyuh183cb712017-12-27 17:02:37 -0800403 final CallerInfoQueryToken queryToken = new CallerInfoQueryToken(queryId, callId);
404 queryId++;
Eric Erfanianccca3152017-02-22 16:32:36 -0800405 final CallerInfo callerInfo =
406 CallerInfoUtils.getCallerInfoForCall(
linyuh183cb712017-12-27 17:02:37 -0800407 context,
Eric Erfanianccca3152017-02-22 16:32:36 -0800408 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700409 new DialerCallCookieWrapper(callId, call.getNumberPresentation(), call.getCnapName()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700410 new FindInfoCallback(isIncoming, queryToken));
wangqic8cf79e2017-10-17 09:21:00 -0700411 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800412
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700413 if (cacheEntry != null) {
414 // We should not override the old cache item until the new query is
415 // back. We should only update the queryId. Otherwise, we may see
416 // flicker of the name and image (old cache -> new cache before query
417 // -> new cache after query)
linyuh183cb712017-12-27 17:02:37 -0800418 cacheEntry.queryId = queryToken.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700419 Log.d(TAG, "There is an existing cache. Do not override until new query is back");
420 } else {
421 ContactCacheEntry initialCacheEntry =
422 updateCallerInfoInCacheOnAnyThread(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700423 callId, call.getNumberPresentation(), callerInfo, false, queryToken);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700424 sendInfoNotifications(callId, initialCacheEntry);
425 }
wangqicf61ca02017-08-31 15:32:55 -0700426 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800427 }
428
429 @AnyThread
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700430 private ContactCacheEntry updateCallerInfoInCacheOnAnyThread(
Eric Erfanianccca3152017-02-22 16:32:36 -0800431 String callId,
432 int numberPresentation,
433 CallerInfo callerInfo,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700434 boolean didLocalLookup,
435 CallerInfoQueryToken queryToken) {
wangqicf61ca02017-08-31 15:32:55 -0700436 Trace.beginSection("ContactInfoCache.updateCallerInfoInCacheOnAnyThread");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700437 Log.d(
438 TAG,
439 "updateCallerInfoInCacheOnAnyThread: callId = "
440 + callId
441 + "; queryId = "
linyuh183cb712017-12-27 17:02:37 -0800442 + queryToken.queryId
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700443 + "; didLocalLookup = "
444 + didLocalLookup);
445
linyuh183cb712017-12-27 17:02:37 -0800446 ContactCacheEntry existingCacheEntry = infoMap.get(callId);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700447 Log.d(TAG, "Existing cacheEntry in hashMap " + existingCacheEntry);
448
449 // Mark it as emergency/voicemail if the cache exists and was emergency/voicemail before the
450 // number changed.
451 if (existingCacheEntry != null) {
452 if (existingCacheEntry.isEmergencyNumber) {
linyuh183cb712017-12-27 17:02:37 -0800453 callerInfo.markAsEmergency(context);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700454 } else if (existingCacheEntry.isVoicemailNumber) {
linyuh183cb712017-12-27 17:02:37 -0800455 callerInfo.markAsVoiceMail(context);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700456 }
457 }
458
Eric Erfanianccca3152017-02-22 16:32:36 -0800459 int presentationMode = numberPresentation;
460 if (callerInfo.contactExists
461 || callerInfo.isEmergencyNumber()
462 || callerInfo.isVoiceMailNumber()) {
463 presentationMode = TelecomManager.PRESENTATION_ALLOWED;
464 }
465
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700466 // We always replace the entry. The only exception is the same photo case.
linyuh183cb712017-12-27 17:02:37 -0800467 ContactCacheEntry cacheEntry = buildEntry(context, callerInfo, presentationMode);
468 cacheEntry.queryId = queryToken.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700469
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700470 if (didLocalLookup) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700471 if (cacheEntry.displayPhotoUri != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700472 // When the difference between 2 numbers is only the prefix (e.g. + or IDD),
473 // we will still trigger force query so that the number can be updated on
474 // the calling screen. We need not query the image again if the previous
475 // query already has the image to avoid flickering.
476 if (existingCacheEntry != null
477 && existingCacheEntry.displayPhotoUri != null
478 && existingCacheEntry.displayPhotoUri.equals(cacheEntry.displayPhotoUri)
479 && existingCacheEntry.photo != null) {
480 Log.d(TAG, "Same picture. Do not need start image load.");
481 cacheEntry.photo = existingCacheEntry.photo;
482 cacheEntry.photoType = existingCacheEntry.photoType;
483 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800484 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700485
486 Log.d(TAG, "Contact lookup. Local contact found, starting image load");
487 // Load the image with a callback to update the image state.
488 // When the load is finished, onImageLoadComplete() will be called.
489 cacheEntry.hasPendingQuery = true;
490 ContactsAsyncHelper.startObtainPhotoAsync(
491 TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
linyuh183cb712017-12-27 17:02:37 -0800492 context,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700493 cacheEntry.displayPhotoUri,
494 ContactInfoCache.this,
495 queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800496 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700497 Log.d(TAG, "put entry into map: " + cacheEntry);
linyuh183cb712017-12-27 17:02:37 -0800498 infoMap.put(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700499 } else {
500 // Don't overwrite if there is existing cache.
501 Log.d(TAG, "put entry into map if not exists: " + cacheEntry);
linyuh183cb712017-12-27 17:02:37 -0800502 infoMap.putIfAbsent(callId, cacheEntry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800503 }
wangqicf61ca02017-08-31 15:32:55 -0700504 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700505 return cacheEntry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800506 }
507
Eric Erfaniand8046e52017-04-06 09:41:50 -0700508 private void maybeUpdateFromCequintCallerId(
509 CallerInfo callerInfo, String cnapName, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800510 if (!CequintCallerIdManager.isCequintCallerIdEnabled(context)) {
Eric Erfanian9779f962017-03-27 12:31:48 -0700511 return;
512 }
Eric Erfaniand8046e52017-04-06 09:41:50 -0700513 if (callerInfo.phoneNumber == null) {
514 return;
515 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700516 CequintCallerIdContact cequintCallerIdContact =
517 CequintCallerIdManager.getCequintCallerIdContactForInCall(
linyuh183cb712017-12-27 17:02:37 -0800518 context, callerInfo.phoneNumber, cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700519
Eric Erfaniand8046e52017-04-06 09:41:50 -0700520 if (cequintCallerIdContact == null) {
521 return;
522 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700523 boolean hasUpdate = false;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700524
linyuh82953532018-04-24 16:06:06 -0700525 if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name())) {
526 callerInfo.name = cequintCallerIdContact.name();
Eric Erfanian8369df02017-05-03 10:27:13 -0700527 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700528 }
linyuh82953532018-04-24 16:06:06 -0700529 if (!TextUtils.isEmpty(cequintCallerIdContact.geolocation())) {
530 callerInfo.geoDescription = cequintCallerIdContact.geolocation();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700531 callerInfo.shouldShowGeoDescription = true;
Eric Erfanian8369df02017-05-03 10:27:13 -0700532 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700533 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700534 // Don't overwrite photo in local contacts.
535 if (!callerInfo.contactExists
536 && callerInfo.contactDisplayPhotoUri == null
linyuh82953532018-04-24 16:06:06 -0700537 && cequintCallerIdContact.photoUri() != null) {
538 callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.photoUri());
Eric Erfanian8369df02017-05-03 10:27:13 -0700539 hasUpdate = true;
Eric Erfanian9779f962017-03-27 12:31:48 -0700540 }
Eric Erfanian8369df02017-05-03 10:27:13 -0700541 // Set contact to exist to avoid phone number service lookup.
wangqid6b91d12018-01-30 16:59:03 -0800542 if (hasUpdate) {
543 callerInfo.contactExists = true;
wangqi262b6f22018-03-16 12:27:56 -0700544 callerInfo.contactLookupResultType = ContactLookupResult.Type.CEQUINT;
wangqid6b91d12018-01-30 16:59:03 -0800545 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700546 }
547
Eric Erfanianccca3152017-02-22 16:32:36 -0800548 /**
549 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. Update contact photo
550 * when image is loaded in worker thread.
551 */
552 @WorkerThread
553 @Override
554 public void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
555 Assert.isWorkerThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700556 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
linyuh183cb712017-12-27 17:02:37 -0800557 final String callId = myCookie.callId;
558 final int queryId = myCookie.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700559 if (!isWaitingForThisQuery(callId, queryId)) {
560 return;
561 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 loadImage(photo, photoIcon, cookie);
563 }
564
565 private void loadImage(Drawable photo, Bitmap photoIcon, Object cookie) {
linyuh183cb712017-12-27 17:02:37 -0800566 Log.d(TAG, "Image load complete with context: ", context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800567 // TODO: may be nice to update the image view again once the newer one
568 // is available on contacts database.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700569 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
linyuh183cb712017-12-27 17:02:37 -0800570 final String callId = myCookie.callId;
571 ContactCacheEntry entry = infoMap.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800572
573 if (entry == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700574 Log.e(TAG, "Image Load received for empty search entry.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 clearCallbacks(callId);
576 return;
577 }
578
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700579 Log.d(TAG, "setting photo for entry: ", entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800580
581 // Conference call icons are being handled in CallCardPresenter.
582 if (photo != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700583 Log.v(TAG, "direct drawable: ", photo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800584 entry.photo = photo;
585 entry.photoType = ContactPhotoType.CONTACT;
586 } else if (photoIcon != null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700587 Log.v(TAG, "photo icon: ", photoIcon);
linyuh183cb712017-12-27 17:02:37 -0800588 entry.photo = new BitmapDrawable(context.getResources(), photoIcon);
Eric Erfanianccca3152017-02-22 16:32:36 -0800589 entry.photoType = ContactPhotoType.CONTACT;
590 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700591 Log.v(TAG, "unknown photo");
Eric Erfanianccca3152017-02-22 16:32:36 -0800592 entry.photo = null;
593 entry.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
594 }
595 }
596
597 /**
598 * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. make sure that the
599 * call state is reflected after the image is loaded.
600 */
601 @MainThread
602 @Override
603 public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
604 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700605 CallerInfoQueryToken myCookie = (CallerInfoQueryToken) cookie;
linyuh183cb712017-12-27 17:02:37 -0800606 final String callId = myCookie.callId;
607 final int queryId = myCookie.queryId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700608 if (!isWaitingForThisQuery(callId, queryId)) {
609 return;
610 }
linyuh183cb712017-12-27 17:02:37 -0800611 sendImageNotifications(callId, infoMap.get(callId));
Eric Erfanianccca3152017-02-22 16:32:36 -0800612
613 clearCallbacks(callId);
614 }
615
616 /** Blows away the stored cache values. */
617 public void clearCache() {
linyuh183cb712017-12-27 17:02:37 -0800618 infoMap.clear();
619 callBacks.clear();
620 queryId = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 }
622
Eric Erfanian8369df02017-05-03 10:27:13 -0700623 private ContactCacheEntry buildEntry(Context context, CallerInfo info, int presentation) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800624 final ContactCacheEntry cce = new ContactCacheEntry();
Eric Erfanian8369df02017-05-03 10:27:13 -0700625 populateCacheEntry(context, info, cce, presentation);
Eric Erfanianccca3152017-02-22 16:32:36 -0800626
627 // This will only be true for emergency numbers
628 if (info.photoResource != 0) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700629 cce.photo = ContextCompat.getDrawable(context, info.photoResource);
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 } else if (info.isCachedPhotoCurrent) {
631 if (info.cachedPhoto != null) {
632 cce.photo = info.cachedPhoto;
633 cce.photoType = ContactPhotoType.CONTACT;
634 } else {
Eric Erfanianccca3152017-02-22 16:32:36 -0800635 cce.photoType = ContactPhotoType.DEFAULT_PLACEHOLDER;
636 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800637 } else {
638 cce.displayPhotoUri = info.contactDisplayPhotoUri;
639 cce.photo = null;
640 }
641
linyuh437ae952018-03-26 12:46:18 -0700642 if (info.lookupKeyOrNull != null && info.contactIdOrZero != 0) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800643 cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
644 } else {
645 Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
646 cce.lookupUri = null;
647 }
648
649 cce.lookupKey = info.lookupKeyOrNull;
650 cce.contactRingtoneUri = info.contactRingtoneUri;
651 if (cce.contactRingtoneUri == null || Uri.EMPTY.equals(cce.contactRingtoneUri)) {
652 cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
653 }
654
655 return cce;
656 }
657
658 /** Sends the updated information to call the callbacks for the entry. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700659 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800660 private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700661 Trace.beginSection("ContactInfoCache.sendInfoNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700662 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -0800663 final Set<ContactInfoCacheCallback> callBacks = this.callBacks.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800664 if (callBacks != null) {
665 for (ContactInfoCacheCallback callBack : callBacks) {
666 callBack.onContactInfoComplete(callId, entry);
667 }
668 }
wangqicf61ca02017-08-31 15:32:55 -0700669 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800670 }
671
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700672 @MainThread
Eric Erfanianccca3152017-02-22 16:32:36 -0800673 private void sendImageNotifications(String callId, ContactCacheEntry entry) {
wangqicf61ca02017-08-31 15:32:55 -0700674 Trace.beginSection("ContactInfoCache.sendImageNotifications");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700675 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -0800676 final Set<ContactInfoCacheCallback> callBacks = this.callBacks.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800677 if (callBacks != null && entry.photo != null) {
678 for (ContactInfoCacheCallback callBack : callBacks) {
679 callBack.onImageLoadComplete(callId, entry);
680 }
681 }
wangqicf61ca02017-08-31 15:32:55 -0700682 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800683 }
684
685 private void clearCallbacks(String callId) {
linyuh183cb712017-12-27 17:02:37 -0800686 callBacks.remove(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800687 }
688
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 /** Callback interface for the contact query. */
690 public interface ContactInfoCacheCallback {
691
692 void onContactInfoComplete(String callId, ContactCacheEntry entry);
693
694 void onImageLoadComplete(String callId, ContactCacheEntry entry);
695 }
696
697 /** This is cached contact info, which should be the ONLY info used by UI. */
698 public static class ContactCacheEntry {
699
700 public String namePrimary;
701 public String nameAlternative;
702 public String number;
703 public String location;
704 public String label;
705 public Drawable photo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700706 @ContactPhotoType int photoType;
707 boolean isSipCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 // Note in cache entry whether this is a pending async loading action to know whether to
709 // wait for its callback or not.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700710 boolean hasPendingQuery;
Eric Erfanianccca3152017-02-22 16:32:36 -0800711 /** Either a display photo or a thumbnail URI. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700712 Uri displayPhotoUri;
Eric Erfanianccca3152017-02-22 16:32:36 -0800713
714 public Uri lookupUri; // Sent to NotificationMananger
715 public String lookupKey;
Eric Erfanian8369df02017-05-03 10:27:13 -0700716 public ContactLookupResult.Type contactLookupResult = ContactLookupResult.Type.NOT_FOUND;
Eric Erfanianccca3152017-02-22 16:32:36 -0800717 public long userType = ContactsUtils.USER_TYPE_CURRENT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700718 Uri contactRingtoneUri;
719 /** Query id to identify the query session. */
720 int queryId;
721 /** The phone number without any changes to display to the user (ex: cnap...) */
722 String originalPhoneNumber;
zachh6a4cebd2017-10-24 17:10:06 -0700723
Eric Erfaniand8046e52017-04-06 09:41:50 -0700724 boolean shouldShowLocation;
Eric Erfanian9779f962017-03-27 12:31:48 -0700725
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700726 boolean isBusiness;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700727 boolean isEmergencyNumber;
728 boolean isVoicemailNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800729
wangqiae6c8ec2017-09-28 17:39:40 -0700730 public boolean isLocalContact() {
731 return contactLookupResult == ContactLookupResult.Type.LOCAL_CONTACT;
732 }
733
Eric Erfanianccca3152017-02-22 16:32:36 -0800734 @Override
735 public String toString() {
736 return "ContactCacheEntry{"
737 + "name='"
738 + MoreStrings.toSafeString(namePrimary)
739 + '\''
740 + ", nameAlternative='"
741 + MoreStrings.toSafeString(nameAlternative)
742 + '\''
743 + ", number='"
744 + MoreStrings.toSafeString(number)
745 + '\''
746 + ", location='"
747 + MoreStrings.toSafeString(location)
748 + '\''
749 + ", label='"
750 + label
751 + '\''
752 + ", photo="
753 + photo
754 + ", isSipCall="
755 + isSipCall
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 + ", displayPhotoUri="
757 + displayPhotoUri
758 + ", contactLookupResult="
759 + contactLookupResult
760 + ", userType="
761 + userType
762 + ", contactRingtoneUri="
763 + contactRingtoneUri
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700764 + ", queryId="
765 + queryId
766 + ", originalPhoneNumber="
767 + originalPhoneNumber
Eric Erfaniand8046e52017-04-06 09:41:50 -0700768 + ", shouldShowLocation="
769 + shouldShowLocation
Eric Erfanian2ca43182017-08-31 06:57:16 -0700770 + ", isEmergencyNumber="
771 + isEmergencyNumber
772 + ", isVoicemailNumber="
773 + isVoicemailNumber
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 + '}';
775 }
776 }
777
778 private static final class DialerCallCookieWrapper {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700779 final String callId;
780 final int numberPresentation;
781 final String cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800782
Eric Erfaniand8046e52017-04-06 09:41:50 -0700783 DialerCallCookieWrapper(String callId, int numberPresentation, String cnapName) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800784 this.callId = callId;
785 this.numberPresentation = numberPresentation;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700786 this.cnapName = cnapName;
Eric Erfanianccca3152017-02-22 16:32:36 -0800787 }
788 }
789
790 private class FindInfoCallback implements OnQueryCompleteListener {
791
linyuh183cb712017-12-27 17:02:37 -0800792 private final boolean isIncoming;
793 private final CallerInfoQueryToken queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800794
Eric Erfaniand8046e52017-04-06 09:41:50 -0700795 FindInfoCallback(boolean isIncoming, CallerInfoQueryToken queryToken) {
linyuh183cb712017-12-27 17:02:37 -0800796 this.isIncoming = isIncoming;
797 this.queryToken = queryToken;
Eric Erfanianccca3152017-02-22 16:32:36 -0800798 }
799
800 @Override
801 public void onDataLoaded(int token, Object cookie, CallerInfo ci) {
802 Assert.isWorkerThread();
803 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
linyuh183cb712017-12-27 17:02:37 -0800804 if (!isWaitingForThisQuery(cw.callId, queryToken.queryId)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700805 return;
806 }
Eric Erfanian9779f962017-03-27 12:31:48 -0700807 long start = SystemClock.uptimeMillis();
linyuh183cb712017-12-27 17:02:37 -0800808 maybeUpdateFromCequintCallerId(ci, cw.cnapName, isIncoming);
Eric Erfanian9779f962017-03-27 12:31:48 -0700809 long time = SystemClock.uptimeMillis() - start;
810 Log.d(TAG, "Cequint Caller Id look up takes " + time + " ms.");
linyuh183cb712017-12-27 17:02:37 -0800811 updateCallerInfoInCacheOnAnyThread(cw.callId, cw.numberPresentation, ci, true, queryToken);
Eric Erfanianccca3152017-02-22 16:32:36 -0800812 }
813
814 @Override
815 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
wangqi9982f0d2017-10-11 17:46:07 -0700816 Trace.beginSection("ContactInfoCache.FindInfoCallback.onQueryComplete");
Eric Erfanianccca3152017-02-22 16:32:36 -0800817 Assert.isMainThread();
818 DialerCallCookieWrapper cw = (DialerCallCookieWrapper) cookie;
819 String callId = cw.callId;
linyuh183cb712017-12-27 17:02:37 -0800820 if (!isWaitingForThisQuery(cw.callId, queryToken.queryId)) {
wangqi9982f0d2017-10-11 17:46:07 -0700821 Trace.endSection();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700822 return;
823 }
linyuh183cb712017-12-27 17:02:37 -0800824 ContactCacheEntry cacheEntry = infoMap.get(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800825 // This may happen only when InCallPresenter attempt to cleanup.
826 if (cacheEntry == null) {
827 Log.w(TAG, "Contact lookup done, but cache entry is not found.");
828 clearCallbacks(callId);
wangqi9982f0d2017-10-11 17:46:07 -0700829 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800830 return;
831 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700832 // Before issuing a request for more data from other services, we only check that the
833 // contact wasn't found in the local DB. We don't check the if the cache entry already
834 // has a name because we allow overriding cnap data with data from other services.
linyuh183cb712017-12-27 17:02:37 -0800835 if (!callerInfo.contactExists && phoneNumberService != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700836 Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
837 final PhoneNumberServiceListener listener =
linyuh183cb712017-12-27 17:02:37 -0800838 new PhoneNumberServiceListener(callId, queryToken.queryId);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700839 cacheEntry.hasPendingQuery = true;
linyuh183cb712017-12-27 17:02:37 -0800840 phoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener, isIncoming);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700841 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800842 sendInfoNotifications(callId, cacheEntry);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700843 if (!cacheEntry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800844 if (callerInfo.contactExists) {
845 Log.d(TAG, "Contact lookup done. Local contact found, no image.");
846 } else {
847 Log.d(
848 TAG,
849 "Contact lookup done. Local contact not found and"
850 + " no remote lookup service available.");
851 }
852 clearCallbacks(callId);
853 }
wangqi9982f0d2017-10-11 17:46:07 -0700854 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 }
856 }
857
858 class PhoneNumberServiceListener
859 implements PhoneNumberService.NumberLookupListener, PhoneNumberService.ImageLookupListener {
860
linyuh183cb712017-12-27 17:02:37 -0800861 private final String callId;
862 private final int queryIdOfRemoteLookup;
Eric Erfanianccca3152017-02-22 16:32:36 -0800863
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700864 PhoneNumberServiceListener(String callId, int queryId) {
linyuh183cb712017-12-27 17:02:37 -0800865 this.callId = callId;
866 queryIdOfRemoteLookup = queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800867 }
868
869 @Override
870 public void onPhoneNumberInfoComplete(final PhoneNumberService.PhoneNumberInfo info) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700871 Log.d(TAG, "PhoneNumberServiceListener.onPhoneNumberInfoComplete");
linyuh183cb712017-12-27 17:02:37 -0800872 if (!isWaitingForThisQuery(callId, queryIdOfRemoteLookup)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700873 return;
874 }
875
Eric Erfanianccca3152017-02-22 16:32:36 -0800876 // If we got a miss, this is the end of the lookup pipeline,
877 // so clear the callbacks and return.
878 if (info == null) {
879 Log.d(TAG, "Contact lookup done. Remote contact not found.");
linyuh183cb712017-12-27 17:02:37 -0800880 clearCallbacks(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800881 return;
882 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800883 ContactCacheEntry entry = new ContactCacheEntry();
884 entry.namePrimary = info.getDisplayName();
885 entry.number = info.getNumber();
886 entry.contactLookupResult = info.getLookupSource();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700887 entry.isBusiness = info.isBusiness();
Eric Erfanianccca3152017-02-22 16:32:36 -0800888 final int type = info.getPhoneType();
889 final String label = info.getPhoneLabel();
890 if (type == Phone.TYPE_CUSTOM) {
891 entry.label = label;
892 } else {
linyuh183cb712017-12-27 17:02:37 -0800893 final CharSequence typeStr = Phone.getTypeLabel(context.getResources(), type, label);
Eric Erfanianccca3152017-02-22 16:32:36 -0800894 entry.label = typeStr == null ? null : typeStr.toString();
895 }
linyuh183cb712017-12-27 17:02:37 -0800896 final ContactCacheEntry oldEntry = infoMap.get(callId);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700897 if (oldEntry != null) {
898 // Location is only obtained from local lookup so persist
899 // the value for remote lookups. Once we have a name this
900 // field is no longer used; it is persisted here in case
901 // the UI is ever changed to use it.
902 entry.location = oldEntry.location;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700903 entry.shouldShowLocation = oldEntry.shouldShowLocation;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700904 // Contact specific ringtone is obtained from local lookup.
905 entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700906 entry.originalPhoneNumber = oldEntry.originalPhoneNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800907 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700908
909 // If no image and it's a business, switch to using the default business avatar.
910 if (info.getImageUrl() == null && info.isBusiness()) {
911 Log.d(TAG, "Business has no image. Using default.");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700912 entry.photoType = ContactPhotoType.BUSINESS;
913 }
914
915 Log.d(TAG, "put entry into map: " + entry);
linyuh183cb712017-12-27 17:02:37 -0800916 infoMap.put(callId, entry);
917 sendInfoNotifications(callId, entry);
Eric Erfanianccca3152017-02-22 16:32:36 -0800918
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700919 entry.hasPendingQuery = info.getImageUrl() != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800920
921 // If there is no image then we should not expect another callback.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700922 if (!entry.hasPendingQuery) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800923 // We're done, so clear callbacks
linyuh183cb712017-12-27 17:02:37 -0800924 clearCallbacks(callId);
Eric Erfanianccca3152017-02-22 16:32:36 -0800925 }
926 }
927
928 @Override
929 public void onImageFetchComplete(Bitmap bitmap) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700930 Log.d(TAG, "PhoneNumberServiceListener.onImageFetchComplete");
linyuh183cb712017-12-27 17:02:37 -0800931 if (!isWaitingForThisQuery(callId, queryIdOfRemoteLookup)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700932 return;
933 }
linyuh183cb712017-12-27 17:02:37 -0800934 CallerInfoQueryToken queryToken = new CallerInfoQueryToken(queryIdOfRemoteLookup, callId);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700935 loadImage(null, bitmap, queryToken);
936 onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, queryToken);
937 }
938 }
939
940 private boolean needForceQuery(DialerCall call, ContactCacheEntry cacheEntry) {
941 if (call == null || call.isConferenceCall()) {
942 return false;
943 }
944
945 String newPhoneNumber = PhoneNumberUtils.stripSeparators(call.getNumber());
946 if (cacheEntry == null) {
947 // No info in the map yet so it is the 1st query
948 Log.d(TAG, "needForceQuery: first query");
949 return true;
950 }
951 String oldPhoneNumber = PhoneNumberUtils.stripSeparators(cacheEntry.originalPhoneNumber);
952
953 if (!TextUtils.equals(oldPhoneNumber, newPhoneNumber)) {
954 Log.d(TAG, "phone number has changed: " + oldPhoneNumber + " -> " + newPhoneNumber);
955 return true;
956 }
957
958 return false;
959 }
960
961 private static final class CallerInfoQueryToken {
linyuh183cb712017-12-27 17:02:37 -0800962 final int queryId;
963 final String callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700964
965 CallerInfoQueryToken(int queryId, String callId) {
linyuh183cb712017-12-27 17:02:37 -0800966 this.queryId = queryId;
967 this.callId = callId;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700968 }
969 }
970
971 /** Check if the queryId in the cached map is the same as the one from query result. */
972 private boolean isWaitingForThisQuery(String callId, int queryId) {
linyuh183cb712017-12-27 17:02:37 -0800973 final ContactCacheEntry existingCacheEntry = infoMap.get(callId);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700974 if (existingCacheEntry == null) {
975 // This might happen if lookup on background thread comes back before the initial entry is
976 // created.
977 Log.d(TAG, "Cached entry is null.");
978 return true;
979 } else {
980 int waitingQueryId = existingCacheEntry.queryId;
981 Log.d(TAG, "waitingQueryId = " + waitingQueryId + "; queryId = " + queryId);
982 return waitingQueryId == queryId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800983 }
984 }
985}