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