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