• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.android.dialer.phonenumbercache;
16 
17 import android.annotation.TargetApi;
18 import android.content.ContentResolver;
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.database.sqlite.SQLiteFullException;
23 import android.net.Uri;
24 import android.os.Build.VERSION;
25 import android.os.Build.VERSION_CODES;
26 import android.provider.CallLog.Calls;
27 import android.provider.ContactsContract;
28 import android.provider.ContactsContract.CommonDataKinds.Phone;
29 import android.provider.ContactsContract.Contacts;
30 import android.provider.ContactsContract.Directory;
31 import android.provider.ContactsContract.DisplayNameSources;
32 import android.provider.ContactsContract.PhoneLookup;
33 import android.support.annotation.Nullable;
34 import android.support.annotation.WorkerThread;
35 import android.telephony.PhoneNumberUtils;
36 import android.text.TextUtils;
37 import com.android.contacts.common.ContactsUtils;
38 import com.android.contacts.common.ContactsUtils.UserType;
39 import com.android.contacts.common.compat.DirectoryCompat;
40 import com.android.contacts.common.util.Constants;
41 import com.android.contacts.common.util.UriUtils;
42 import com.android.dialer.common.Assert;
43 import com.android.dialer.common.LogUtil;
44 import com.android.dialer.logging.ContactSource;
45 import com.android.dialer.oem.CequintCallerIdManager;
46 import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
47 import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
48 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
49 import com.android.dialer.telecom.TelecomUtil;
50 import com.android.dialer.util.PermissionsUtil;
51 import java.util.ArrayList;
52 import java.util.List;
53 import org.json.JSONException;
54 import org.json.JSONObject;
55 
56 /** Utility class to look up the contact information for a given number. */
57 // This class uses Java 7 language features, so it must target M+
58 @TargetApi(VERSION_CODES.M)
59 public class ContactInfoHelper {
60 
61   private static final String TAG = ContactInfoHelper.class.getSimpleName();
62 
63   private final Context mContext;
64   private final String mCurrentCountryIso;
65   private final CachedNumberLookupService mCachedNumberLookupService;
66 
ContactInfoHelper(Context context, String currentCountryIso)67   public ContactInfoHelper(Context context, String currentCountryIso) {
68     mContext = context;
69     mCurrentCountryIso = currentCountryIso;
70     mCachedNumberLookupService = PhoneNumberCache.get(mContext).getCachedNumberLookupService();
71   }
72 
73   /**
74    * Creates a JSON-encoded lookup uri for a unknown number without an associated contact
75    *
76    * @param number - Unknown phone number
77    * @return JSON-encoded URI that can be used to perform a lookup when clicking on the quick
78    *     contact card.
79    */
createTemporaryContactUri(String number)80   private static Uri createTemporaryContactUri(String number) {
81     try {
82       final JSONObject contactRows =
83           new JSONObject()
84               .put(
85                   Phone.CONTENT_ITEM_TYPE,
86                   new JSONObject().put(Phone.NUMBER, number).put(Phone.TYPE, Phone.TYPE_CUSTOM));
87 
88       final String jsonString =
89           new JSONObject()
90               .put(Contacts.DISPLAY_NAME, number)
91               .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.PHONE)
92               .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
93               .toString();
94 
95       return Contacts.CONTENT_LOOKUP_URI
96           .buildUpon()
97           .appendPath(Constants.LOOKUP_URI_ENCODED)
98           .appendQueryParameter(
99               ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Long.MAX_VALUE))
100           .encodedFragment(jsonString)
101           .build();
102     } catch (JSONException e) {
103       return null;
104     }
105   }
106 
lookUpDisplayNameAlternative( Context context, String lookupKey, @UserType long userType, @Nullable Long directoryId)107   public static String lookUpDisplayNameAlternative(
108       Context context, String lookupKey, @UserType long userType, @Nullable Long directoryId) {
109     // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
110     if (lookupKey == null || userType == ContactsUtils.USER_TYPE_WORK) {
111       return null;
112     }
113 
114     if (directoryId != null) {
115       // Query {@link Contacts#CONTENT_LOOKUP_URI} with work lookup key is not allowed.
116       if (DirectoryCompat.isEnterpriseDirectoryId(directoryId)) {
117         return null;
118       }
119 
120       // Skip this to avoid an extra remote network call for alternative name
121       if (DirectoryCompat.isRemoteDirectoryId(directoryId)) {
122         return null;
123       }
124     }
125 
126     final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
127     Cursor cursor = null;
128     try {
129       cursor =
130           context
131               .getContentResolver()
132               .query(uri, PhoneQuery.DISPLAY_NAME_ALTERNATIVE_PROJECTION, null, null, null);
133 
134       if (cursor != null && cursor.moveToFirst()) {
135         return cursor.getString(PhoneQuery.NAME_ALTERNATIVE);
136       }
137     } catch (IllegalArgumentException e) {
138       // Avoid dialer crash when lookup key is not valid
139       LogUtil.e(TAG, "IllegalArgumentException in lookUpDisplayNameAlternative", e);
140     } finally {
141       if (cursor != null) {
142         cursor.close();
143       }
144     }
145 
146     return null;
147   }
148 
getContactInfoLookupUri(String number)149   public static Uri getContactInfoLookupUri(String number) {
150     return getContactInfoLookupUri(number, -1);
151   }
152 
getContactInfoLookupUri(String number, long directoryId)153   public static Uri getContactInfoLookupUri(String number, long directoryId) {
154     // Get URI for the number in the PhoneLookup table, with a parameter to indicate whether
155     // the number is a SIP number.
156     Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
157     if (VERSION.SDK_INT < VERSION_CODES.N) {
158       if (directoryId != -1) {
159         // ENTERPRISE_CONTENT_FILTER_URI in M doesn't support directory lookup
160         uri = PhoneLookup.CONTENT_FILTER_URI;
161       } else {
162         // b/25900607 in M. PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, encodes twice.
163         number = Uri.encode(number);
164       }
165     }
166     Uri.Builder builder =
167         uri.buildUpon()
168             .appendPath(number)
169             .appendQueryParameter(
170                 PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
171                 String.valueOf(PhoneNumberHelper.isUriNumber(number)));
172     if (directoryId != -1) {
173       builder.appendQueryParameter(
174           ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId));
175     }
176     return builder.build();
177   }
178 
179   /**
180    * Returns the contact information stored in an entry of the call log.
181    *
182    * @param c A cursor pointing to an entry in the call log.
183    */
getContactInfo(Cursor c)184   public static ContactInfo getContactInfo(Cursor c) {
185     ContactInfo info = new ContactInfo();
186     info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI));
187     info.name = c.getString(CallLogQuery.CACHED_NAME);
188     info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
189     info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
190     String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER);
191     String postDialDigits =
192         (VERSION.SDK_INT >= VERSION_CODES.N) ? c.getString(CallLogQuery.POST_DIAL_DIGITS) : "";
193     info.number =
194         (matchedNumber == null) ? c.getString(CallLogQuery.NUMBER) + postDialDigits : matchedNumber;
195 
196     info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
197     info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
198     info.photoUri =
199         UriUtils.nullForNonContactsUri(
200             UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_PHOTO_URI)));
201     info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
202 
203     return info;
204   }
205 
206   @Nullable
lookupNumber(String number, String countryIso)207   public ContactInfo lookupNumber(String number, String countryIso) {
208     return lookupNumber(number, countryIso, -1);
209   }
210 
211   /**
212    * Returns the contact information for the given number.
213    *
214    * <p>If the number does not match any contact, returns a contact info containing only the number
215    * and the formatted number.
216    *
217    * <p>If an error occurs during the lookup, it returns null.
218    *
219    * @param number the number to look up
220    * @param countryIso the country associated with this number
221    * @param directoryId the id of the directory to lookup
222    */
223   @Nullable
224   @SuppressWarnings("ReferenceEquality")
lookupNumber(String number, String countryIso, long directoryId)225   public ContactInfo lookupNumber(String number, String countryIso, long directoryId) {
226     if (TextUtils.isEmpty(number)) {
227       LogUtil.d("ContactInfoHelper.lookupNumber", "number is empty");
228       return null;
229     }
230 
231     ContactInfo info;
232 
233     if (PhoneNumberHelper.isUriNumber(number)) {
234       LogUtil.d("ContactInfoHelper.lookupNumber", "number is sip");
235       // The number is a SIP address..
236       info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId));
237       if (info == null || info == ContactInfo.EMPTY) {
238         // If lookup failed, check if the "username" of the SIP address is a phone number.
239         String username = PhoneNumberHelper.getUsernameFromUriNumber(number);
240         if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
241           info = queryContactInfoForPhoneNumber(username, countryIso, directoryId);
242         }
243       }
244     } else {
245       // Look for a contact that has the given phone number.
246       info = queryContactInfoForPhoneNumber(number, countryIso, directoryId);
247     }
248 
249     final ContactInfo updatedInfo;
250     if (info == null) {
251       // The lookup failed.
252       LogUtil.d("ContactInfoHelper.lookupNumber", "lookup failed");
253       updatedInfo = null;
254     } else {
255       // If we did not find a matching contact, generate an empty contact info for the number.
256       if (info == ContactInfo.EMPTY) {
257         // Did not find a matching contact.
258         updatedInfo = createEmptyContactInfoForNumber(number, countryIso);
259       } else {
260         updatedInfo = info;
261       }
262     }
263     return updatedInfo;
264   }
265 
createEmptyContactInfoForNumber(String number, String countryIso)266   private ContactInfo createEmptyContactInfoForNumber(String number, String countryIso) {
267     ContactInfo contactInfo = new ContactInfo();
268     contactInfo.number = number;
269     contactInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
270     contactInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
271     contactInfo.lookupUri = createTemporaryContactUri(contactInfo.formattedNumber);
272     return contactInfo;
273   }
274 
275   /**
276    * Return the contact info object if the remote directory lookup succeeds, otherwise return an
277    * empty contact info for the number.
278    */
lookupNumberInRemoteDirectory(String number, String countryIso)279   public ContactInfo lookupNumberInRemoteDirectory(String number, String countryIso) {
280     if (mCachedNumberLookupService != null) {
281       List<Long> remoteDirectories = getRemoteDirectories(mContext);
282       for (long directoryId : remoteDirectories) {
283         ContactInfo contactInfo = lookupNumber(number, countryIso, directoryId);
284         if (hasName(contactInfo)) {
285           return contactInfo;
286         }
287       }
288     }
289     return createEmptyContactInfoForNumber(number, countryIso);
290   }
291 
hasName(ContactInfo contactInfo)292   public boolean hasName(ContactInfo contactInfo) {
293     return contactInfo != null && !TextUtils.isEmpty(contactInfo.name);
294   }
295 
getRemoteDirectories(Context context)296   private List<Long> getRemoteDirectories(Context context) {
297     List<Long> remoteDirectories = new ArrayList<>();
298     Uri uri =
299         VERSION.SDK_INT >= VERSION_CODES.N
300             ? Directory.ENTERPRISE_CONTENT_URI
301             : Directory.CONTENT_URI;
302     ContentResolver cr = context.getContentResolver();
303     Cursor cursor = cr.query(uri, new String[] {Directory._ID}, null, null, null);
304     int idIndex = cursor.getColumnIndex(Directory._ID);
305     if (cursor == null) {
306       return remoteDirectories;
307     }
308     try {
309       while (cursor.moveToNext()) {
310         long directoryId = cursor.getLong(idIndex);
311         if (DirectoryCompat.isRemoteDirectoryId(directoryId)) {
312           remoteDirectories.add(directoryId);
313         }
314       }
315     } finally {
316       cursor.close();
317     }
318     return remoteDirectories;
319   }
320 
321   /**
322    * Looks up a contact using the given URI.
323    *
324    * <p>It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is
325    * found, or the {@link ContactInfo} for the given contact.
326    *
327    * <p>The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned
328    * value.
329    */
lookupContactFromUri(Uri uri)330   ContactInfo lookupContactFromUri(Uri uri) {
331     if (uri == null) {
332       LogUtil.d("ContactInfoHelper.lookupContactFromUri", "uri is null");
333       return null;
334     }
335     if (!PermissionsUtil.hasContactsReadPermissions(mContext)) {
336       LogUtil.d("ContactInfoHelper.lookupContactFromUri", "no contact permission, return empty");
337       return ContactInfo.EMPTY;
338     }
339 
340     Cursor phoneLookupCursor = null;
341     try {
342       String[] projection = PhoneQuery.getPhoneLookupProjection(uri);
343       phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, null);
344     } catch (NullPointerException e) {
345       LogUtil.e("ContactInfoHelper.lookupContactFromUri", "phone lookup", e);
346       // Trap NPE from pre-N CP2
347       return null;
348     }
349     if (phoneLookupCursor == null) {
350       LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null");
351       return null;
352     }
353 
354     try {
355       if (!phoneLookupCursor.moveToFirst()) {
356         return ContactInfo.EMPTY;
357       }
358       String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY);
359       ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey);
360       fillAdditionalContactInfo(mContext, contactInfo);
361       return contactInfo;
362     } finally {
363       phoneLookupCursor.close();
364     }
365   }
366 
createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey)367   private ContactInfo createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey) {
368     ContactInfo info = new ContactInfo();
369     info.lookupKey = lookupKey;
370     info.lookupUri =
371         Contacts.getLookupUri(phoneLookupCursor.getLong(PhoneQuery.PERSON_ID), lookupKey);
372     info.name = phoneLookupCursor.getString(PhoneQuery.NAME);
373     info.type = phoneLookupCursor.getInt(PhoneQuery.PHONE_TYPE);
374     info.label = phoneLookupCursor.getString(PhoneQuery.LABEL);
375     info.number = phoneLookupCursor.getString(PhoneQuery.MATCHED_NUMBER);
376     info.normalizedNumber = phoneLookupCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
377     info.photoId = phoneLookupCursor.getLong(PhoneQuery.PHOTO_ID);
378     info.photoUri = UriUtils.parseUriOrNull(phoneLookupCursor.getString(PhoneQuery.PHOTO_URI));
379     info.formattedNumber = null;
380     info.userType =
381         ContactsUtils.determineUserType(null, phoneLookupCursor.getLong(PhoneQuery.PERSON_ID));
382     info.contactExists = true;
383 
384     return info;
385   }
386 
fillAdditionalContactInfo(Context context, ContactInfo contactInfo)387   private void fillAdditionalContactInfo(Context context, ContactInfo contactInfo) {
388     if (contactInfo.number == null) {
389       return;
390     }
391     Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(contactInfo.number));
392     try (Cursor cursor =
393         context
394             .getContentResolver()
395             .query(uri, PhoneQuery.ADDITIONAL_CONTACT_INFO_PROJECTION, null, null, null)) {
396       if (cursor == null || !cursor.moveToFirst()) {
397         return;
398       }
399       contactInfo.nameAlternative =
400           cursor.getString(PhoneQuery.ADDITIONAL_CONTACT_INFO_DISPLAY_NAME_ALTERNATIVE);
401       contactInfo.carrierPresence =
402           cursor.getInt(PhoneQuery.ADDITIONAL_CONTACT_INFO_CARRIER_PRESENCE);
403     }
404   }
405 
406   /**
407    * Determines the contact information for the given phone number.
408    *
409    * <p>It returns the contact info if found.
410    *
411    * <p>If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
412    *
413    * <p>If the lookup fails for some other reason, it returns null.
414    */
415   @SuppressWarnings("ReferenceEquality")
queryContactInfoForPhoneNumber( String number, String countryIso, long directoryId)416   private ContactInfo queryContactInfoForPhoneNumber(
417       String number, String countryIso, long directoryId) {
418     if (TextUtils.isEmpty(number)) {
419       LogUtil.d("ContactInfoHelper.queryContactInfoForPhoneNumber", "number is empty");
420       return null;
421     }
422 
423     ContactInfo info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId));
424     if (info == null) {
425       LogUtil.d("ContactInfoHelper.queryContactInfoForPhoneNumber", "info looked up is null");
426     }
427     if (info != null && info != ContactInfo.EMPTY) {
428       info.formattedNumber = formatPhoneNumber(number, null, countryIso);
429       if (directoryId == -1) {
430         // Contact found in the default directory
431         info.sourceType = ContactSource.Type.SOURCE_TYPE_DIRECTORY;
432       } else {
433         // Contact found in the extended directory specified by directoryId
434         info.sourceType = ContactSource.Type.SOURCE_TYPE_EXTENDED;
435       }
436     } else if (mCachedNumberLookupService != null) {
437       CachedContactInfo cacheInfo =
438           mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number);
439       if (cacheInfo != null) {
440         if (!cacheInfo.getContactInfo().isBadData) {
441           info = cacheInfo.getContactInfo();
442         } else {
443           LogUtil.i("ContactInfoHelper.queryContactInfoForPhoneNumber", "info is bad data");
444         }
445       }
446     }
447     return info;
448   }
449 
450   /**
451    * Format the given phone number
452    *
453    * @param number the number to be formatted.
454    * @param normalizedNumber the normalized number of the given number.
455    * @param countryIso the ISO 3166-1 two letters country code, the country's convention will be
456    *     used to format the number if the normalized phone is null.
457    * @return the formatted number, or the given number if it was formatted.
458    */
formatPhoneNumber(String number, String normalizedNumber, String countryIso)459   private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) {
460     if (TextUtils.isEmpty(number)) {
461       return "";
462     }
463     // If "number" is really a SIP address, don't try to do any formatting at all.
464     if (PhoneNumberHelper.isUriNumber(number)) {
465       return number;
466     }
467     if (TextUtils.isEmpty(countryIso)) {
468       countryIso = mCurrentCountryIso;
469     }
470     return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
471   }
472 
473   /**
474    * Stores differences between the updated contact info and the current call log contact info.
475    *
476    * @param number The number of the contact.
477    * @param countryIso The country associated with this number.
478    * @param updatedInfo The updated contact info.
479    * @param callLogInfo The call log entry's current contact info.
480    */
updateCallLogContactInfo( String number, String countryIso, ContactInfo updatedInfo, ContactInfo callLogInfo)481   public void updateCallLogContactInfo(
482       String number, String countryIso, ContactInfo updatedInfo, ContactInfo callLogInfo) {
483     if (!PermissionsUtil.hasPermission(mContext, android.Manifest.permission.WRITE_CALL_LOG)) {
484       return;
485     }
486 
487     final ContentValues values = new ContentValues();
488     boolean needsUpdate = false;
489 
490     if (callLogInfo != null) {
491       if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
492         values.put(Calls.CACHED_NAME, updatedInfo.name);
493         needsUpdate = true;
494       }
495 
496       if (updatedInfo.type != callLogInfo.type) {
497         values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
498         needsUpdate = true;
499       }
500 
501       if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
502         values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
503         needsUpdate = true;
504       }
505 
506       if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) {
507         values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
508         needsUpdate = true;
509       }
510 
511       // Only replace the normalized number if the new updated normalized number isn't empty.
512       if (!TextUtils.isEmpty(updatedInfo.normalizedNumber)
513           && !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) {
514         values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
515         needsUpdate = true;
516       }
517 
518       if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) {
519         values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
520         needsUpdate = true;
521       }
522 
523       if (updatedInfo.photoId != callLogInfo.photoId) {
524         values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
525         needsUpdate = true;
526       }
527 
528       final Uri updatedPhotoUriContactsOnly = UriUtils.nullForNonContactsUri(updatedInfo.photoUri);
529       if (!UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) {
530         values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(updatedPhotoUriContactsOnly));
531         needsUpdate = true;
532       }
533 
534       if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) {
535         values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
536         needsUpdate = true;
537       }
538 
539       if (!TextUtils.equals(updatedInfo.geoDescription, callLogInfo.geoDescription)) {
540         values.put(Calls.GEOCODED_LOCATION, updatedInfo.geoDescription);
541         needsUpdate = true;
542       }
543     } else {
544       // No previous values, store all of them.
545       values.put(Calls.CACHED_NAME, updatedInfo.name);
546       values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
547       values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
548       values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
549       values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
550       values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
551       values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
552       values.put(
553           Calls.CACHED_PHOTO_URI,
554           UriUtils.uriToString(UriUtils.nullForNonContactsUri(updatedInfo.photoUri)));
555       values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
556       values.put(Calls.GEOCODED_LOCATION, updatedInfo.geoDescription);
557       needsUpdate = true;
558     }
559 
560     if (!needsUpdate) {
561       return;
562     }
563 
564     try {
565       if (countryIso == null) {
566         mContext
567             .getContentResolver()
568             .update(
569                 TelecomUtil.getCallLogUri(mContext),
570                 values,
571                 Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL",
572                 new String[] {number});
573       } else {
574         mContext
575             .getContentResolver()
576             .update(
577                 TelecomUtil.getCallLogUri(mContext),
578                 values,
579                 Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?",
580                 new String[] {number, countryIso});
581       }
582     } catch (SQLiteFullException e) {
583       LogUtil.e(TAG, "Unable to update contact info in call log db", e);
584     }
585   }
586 
updateCachedNumberLookupService(ContactInfo updatedInfo)587   public void updateCachedNumberLookupService(ContactInfo updatedInfo) {
588     if (mCachedNumberLookupService != null) {
589       if (hasName(updatedInfo)) {
590         CachedContactInfo cachedContactInfo =
591             mCachedNumberLookupService.buildCachedContactInfo(updatedInfo);
592         mCachedNumberLookupService.addContact(mContext, cachedContactInfo);
593       }
594     }
595   }
596 
597   /**
598    * Given a contact's sourceType, return true if the contact is a business
599    *
600    * @param sourceType sourceType of the contact. This is usually populated by {@link
601    *     #mCachedNumberLookupService}.
602    */
isBusiness(ContactSource.Type sourceType)603   public boolean isBusiness(ContactSource.Type sourceType) {
604     return mCachedNumberLookupService != null && mCachedNumberLookupService.isBusiness(sourceType);
605   }
606 
607   /**
608    * This function looks at a contact's source and determines if the user can mark caller ids from
609    * this source as invalid.
610    *
611    * @param sourceType The source type to be checked
612    * @param objectId The ID of the Contact object.
613    * @return true if contacts from this source can be marked with an invalid caller id
614    */
canReportAsInvalid(ContactSource.Type sourceType, String objectId)615   public boolean canReportAsInvalid(ContactSource.Type sourceType, String objectId) {
616     return mCachedNumberLookupService != null
617         && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId);
618   }
619 
620   /**
621    * Update ContactInfo by querying to Cequint Caller ID. Only name, geoDescription and photo uri
622    * will be updated if available.
623    */
624   @WorkerThread
updateFromCequintCallerId( @ullable CequintCallerIdManager cequintCallerIdManager, ContactInfo info, String number)625   public void updateFromCequintCallerId(
626       @Nullable CequintCallerIdManager cequintCallerIdManager, ContactInfo info, String number) {
627     Assert.isWorkerThread();
628     if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) {
629       return;
630     }
631     if (cequintCallerIdManager == null) {
632       return;
633     }
634     CequintCallerIdContact cequintCallerIdContact =
635         cequintCallerIdManager.getCequintCallerIdContact(mContext, number);
636     if (cequintCallerIdContact == null) {
637       return;
638     }
639     if (TextUtils.isEmpty(info.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) {
640       info.name = cequintCallerIdContact.name;
641     }
642     if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) {
643       info.geoDescription = cequintCallerIdContact.geoDescription;
644       info.sourceType = ContactSource.Type.SOURCE_TYPE_CEQUINT_CALLER_ID;
645     }
646     // Only update photo if local lookup has no result.
647     if (!info.contactExists && info.photoUri == null && cequintCallerIdContact.imageUrl != null) {
648       info.photoUri = UriUtils.parseUriOrNull(cequintCallerIdContact.imageUrl);
649     }
650   }
651 }
652