ContactInfoHelper.java revision e7ea93d5235c097151e40f8922a1efb000734904
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 15package com.android.dialer.phonenumbercache; 16 17import android.annotation.TargetApi; 18import android.content.ContentResolver; 19import android.content.ContentValues; 20import android.content.Context; 21import android.database.Cursor; 22import android.database.sqlite.SQLiteFullException; 23import android.net.Uri; 24import android.os.Build.VERSION; 25import android.os.Build.VERSION_CODES; 26import android.provider.CallLog.Calls; 27import android.provider.ContactsContract; 28import android.provider.ContactsContract.CommonDataKinds.Phone; 29import android.provider.ContactsContract.Contacts; 30import android.provider.ContactsContract.Directory; 31import android.provider.ContactsContract.DisplayNameSources; 32import android.provider.ContactsContract.PhoneLookup; 33import android.support.annotation.Nullable; 34import android.support.annotation.WorkerThread; 35import android.telephony.PhoneNumberUtils; 36import android.text.TextUtils; 37import com.android.contacts.common.ContactsUtils; 38import com.android.contacts.common.ContactsUtils.UserType; 39import com.android.contacts.common.compat.DirectoryCompat; 40import com.android.contacts.common.util.Constants; 41import com.android.dialer.common.Assert; 42import com.android.dialer.common.LogUtil; 43import com.android.dialer.logging.ContactSource; 44import com.android.dialer.oem.CequintCallerIdManager; 45import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact; 46import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo; 47import com.android.dialer.phonenumberutil.PhoneNumberHelper; 48import com.android.dialer.telecom.TelecomUtil; 49import com.android.dialer.util.PermissionsUtil; 50import com.android.dialer.util.UriUtils; 51import java.util.ArrayList; 52import java.util.List; 53import org.json.JSONException; 54import 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) 59public 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 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 */ 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 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 149 public static Uri getContactInfoLookupUri(String number) { 150 return getContactInfoLookupUri(number, -1); 151 } 152 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 // a bug 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 */ 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 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") 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 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 */ 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 292 public boolean hasName(ContactInfo contactInfo) { 293 return contactInfo != null && !TextUtils.isEmpty(contactInfo.name); 294 } 295 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 */ 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 try (Cursor phoneLookupCursor = 341 mContext 342 .getContentResolver() 343 .query( 344 uri, 345 PhoneQuery.getPhoneLookupProjection(uri), 346 null /* selection */, 347 null /* selectionArgs */, 348 null /* sortOrder */)) { 349 if (phoneLookupCursor == null) { 350 LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null"); 351 return null; 352 } 353 354 if (!phoneLookupCursor.moveToFirst()) { 355 return ContactInfo.EMPTY; 356 } 357 358 String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY); 359 ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey); 360 fillAdditionalContactInfo(mContext, contactInfo); 361 return contactInfo; 362 } 363 } 364 365 private ContactInfo createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey) { 366 ContactInfo info = new ContactInfo(); 367 info.lookupKey = lookupKey; 368 info.lookupUri = 369 Contacts.getLookupUri(phoneLookupCursor.getLong(PhoneQuery.PERSON_ID), lookupKey); 370 info.name = phoneLookupCursor.getString(PhoneQuery.NAME); 371 info.type = phoneLookupCursor.getInt(PhoneQuery.PHONE_TYPE); 372 info.label = phoneLookupCursor.getString(PhoneQuery.LABEL); 373 info.number = phoneLookupCursor.getString(PhoneQuery.MATCHED_NUMBER); 374 info.normalizedNumber = phoneLookupCursor.getString(PhoneQuery.NORMALIZED_NUMBER); 375 info.photoId = phoneLookupCursor.getLong(PhoneQuery.PHOTO_ID); 376 info.photoUri = UriUtils.parseUriOrNull(phoneLookupCursor.getString(PhoneQuery.PHOTO_URI)); 377 info.formattedNumber = null; 378 info.userType = 379 ContactsUtils.determineUserType(null, phoneLookupCursor.getLong(PhoneQuery.PERSON_ID)); 380 info.contactExists = true; 381 382 return info; 383 } 384 385 private void fillAdditionalContactInfo(Context context, ContactInfo contactInfo) { 386 if (contactInfo.number == null) { 387 return; 388 } 389 Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(contactInfo.number)); 390 try (Cursor cursor = 391 context 392 .getContentResolver() 393 .query(uri, PhoneQuery.ADDITIONAL_CONTACT_INFO_PROJECTION, null, null, null)) { 394 if (cursor == null || !cursor.moveToFirst()) { 395 return; 396 } 397 contactInfo.nameAlternative = 398 cursor.getString(PhoneQuery.ADDITIONAL_CONTACT_INFO_DISPLAY_NAME_ALTERNATIVE); 399 contactInfo.carrierPresence = 400 cursor.getInt(PhoneQuery.ADDITIONAL_CONTACT_INFO_CARRIER_PRESENCE); 401 } 402 } 403 404 /** 405 * Determines the contact information for the given phone number. 406 * 407 * <p>It returns the contact info if found. 408 * 409 * <p>If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}. 410 * 411 * <p>If the lookup fails for some other reason, it returns null. 412 */ 413 @SuppressWarnings("ReferenceEquality") 414 private ContactInfo queryContactInfoForPhoneNumber( 415 String number, String countryIso, long directoryId) { 416 if (TextUtils.isEmpty(number)) { 417 LogUtil.d("ContactInfoHelper.queryContactInfoForPhoneNumber", "number is empty"); 418 return null; 419 } 420 421 ContactInfo info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId)); 422 if (info == null) { 423 LogUtil.d("ContactInfoHelper.queryContactInfoForPhoneNumber", "info looked up is null"); 424 } 425 if (info != null && info != ContactInfo.EMPTY) { 426 info.formattedNumber = formatPhoneNumber(number, null, countryIso); 427 if (directoryId == -1) { 428 // Contact found in the default directory 429 info.sourceType = ContactSource.Type.SOURCE_TYPE_DIRECTORY; 430 } else { 431 // Contact found in the extended directory specified by directoryId 432 info.sourceType = ContactSource.Type.SOURCE_TYPE_EXTENDED; 433 } 434 } else if (mCachedNumberLookupService != null) { 435 CachedContactInfo cacheInfo = 436 mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number); 437 if (cacheInfo != null) { 438 if (!cacheInfo.getContactInfo().isBadData) { 439 info = cacheInfo.getContactInfo(); 440 } else { 441 LogUtil.i("ContactInfoHelper.queryContactInfoForPhoneNumber", "info is bad data"); 442 } 443 } 444 } 445 return info; 446 } 447 448 /** 449 * Format the given phone number 450 * 451 * @param number the number to be formatted. 452 * @param normalizedNumber the normalized number of the given number. 453 * @param countryIso the ISO 3166-1 two letters country code, the country's convention will be 454 * used to format the number if the normalized phone is null. 455 * @return the formatted number, or the given number if it was formatted. 456 */ 457 private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) { 458 if (TextUtils.isEmpty(number)) { 459 return ""; 460 } 461 // If "number" is really a SIP address, don't try to do any formatting at all. 462 if (PhoneNumberHelper.isUriNumber(number)) { 463 return number; 464 } 465 if (TextUtils.isEmpty(countryIso)) { 466 countryIso = mCurrentCountryIso; 467 } 468 return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso); 469 } 470 471 /** 472 * Stores differences between the updated contact info and the current call log contact info. 473 * 474 * @param number The number of the contact. 475 * @param countryIso The country associated with this number. 476 * @param updatedInfo The updated contact info. 477 * @param callLogInfo The call log entry's current contact info. 478 */ 479 public void updateCallLogContactInfo( 480 String number, String countryIso, ContactInfo updatedInfo, ContactInfo callLogInfo) { 481 if (!PermissionsUtil.hasPermission(mContext, android.Manifest.permission.WRITE_CALL_LOG)) { 482 return; 483 } 484 485 final ContentValues values = new ContentValues(); 486 boolean needsUpdate = false; 487 488 if (callLogInfo != null) { 489 if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) { 490 values.put(Calls.CACHED_NAME, updatedInfo.name); 491 needsUpdate = true; 492 } 493 494 if (updatedInfo.type != callLogInfo.type) { 495 values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type); 496 needsUpdate = true; 497 } 498 499 if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) { 500 values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label); 501 needsUpdate = true; 502 } 503 504 if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) { 505 values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri)); 506 needsUpdate = true; 507 } 508 509 // Only replace the normalized number if the new updated normalized number isn't empty. 510 if (!TextUtils.isEmpty(updatedInfo.normalizedNumber) 511 && !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) { 512 values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber); 513 needsUpdate = true; 514 } 515 516 if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) { 517 values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number); 518 needsUpdate = true; 519 } 520 521 if (updatedInfo.photoId != callLogInfo.photoId) { 522 values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId); 523 needsUpdate = true; 524 } 525 526 final Uri updatedPhotoUriContactsOnly = UriUtils.nullForNonContactsUri(updatedInfo.photoUri); 527 if (!UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) { 528 values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(updatedPhotoUriContactsOnly)); 529 needsUpdate = true; 530 } 531 532 if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) { 533 values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber); 534 needsUpdate = true; 535 } 536 537 if (!TextUtils.equals(updatedInfo.geoDescription, callLogInfo.geoDescription)) { 538 values.put(Calls.GEOCODED_LOCATION, updatedInfo.geoDescription); 539 needsUpdate = true; 540 } 541 } else { 542 // No previous values, store all of them. 543 values.put(Calls.CACHED_NAME, updatedInfo.name); 544 values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type); 545 values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label); 546 values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri)); 547 values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number); 548 values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber); 549 values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId); 550 values.put( 551 Calls.CACHED_PHOTO_URI, 552 UriUtils.uriToString(UriUtils.nullForNonContactsUri(updatedInfo.photoUri))); 553 values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber); 554 values.put(Calls.GEOCODED_LOCATION, updatedInfo.geoDescription); 555 needsUpdate = true; 556 } 557 558 if (!needsUpdate) { 559 return; 560 } 561 562 try { 563 if (countryIso == null) { 564 mContext 565 .getContentResolver() 566 .update( 567 TelecomUtil.getCallLogUri(mContext), 568 values, 569 Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL", 570 new String[] {number}); 571 } else { 572 mContext 573 .getContentResolver() 574 .update( 575 TelecomUtil.getCallLogUri(mContext), 576 values, 577 Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?", 578 new String[] {number, countryIso}); 579 } 580 } catch (SQLiteFullException e) { 581 LogUtil.e(TAG, "Unable to update contact info in call log db", e); 582 } 583 } 584 585 public void updateCachedNumberLookupService(ContactInfo updatedInfo) { 586 if (mCachedNumberLookupService != null) { 587 if (hasName(updatedInfo)) { 588 CachedContactInfo cachedContactInfo = 589 mCachedNumberLookupService.buildCachedContactInfo(updatedInfo); 590 mCachedNumberLookupService.addContact(mContext, cachedContactInfo); 591 } 592 } 593 } 594 595 /** 596 * Given a contact's sourceType, return true if the contact is a business 597 * 598 * @param sourceType sourceType of the contact. This is usually populated by {@link 599 * #mCachedNumberLookupService}. 600 */ 601 public boolean isBusiness(ContactSource.Type sourceType) { 602 return mCachedNumberLookupService != null && mCachedNumberLookupService.isBusiness(sourceType); 603 } 604 605 /** 606 * This function looks at a contact's source and determines if the user can mark caller ids from 607 * this source as invalid. 608 * 609 * @param sourceType The source type to be checked 610 * @param objectId The ID of the Contact object. 611 * @return true if contacts from this source can be marked with an invalid caller id 612 */ 613 public boolean canReportAsInvalid(ContactSource.Type sourceType, String objectId) { 614 return mCachedNumberLookupService != null 615 && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId); 616 } 617 618 /** 619 * Update ContactInfo by querying to Cequint Caller ID. Only name, geoDescription and photo uri 620 * will be updated if available. 621 */ 622 @WorkerThread 623 public void updateFromCequintCallerId( 624 @Nullable CequintCallerIdManager cequintCallerIdManager, ContactInfo info, String number) { 625 Assert.isWorkerThread(); 626 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) { 627 return; 628 } 629 if (cequintCallerIdManager == null) { 630 return; 631 } 632 CequintCallerIdContact cequintCallerIdContact = 633 cequintCallerIdManager.getCequintCallerIdContact(mContext, number); 634 if (cequintCallerIdContact == null) { 635 return; 636 } 637 if (TextUtils.isEmpty(info.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) { 638 info.name = cequintCallerIdContact.name; 639 } 640 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) { 641 info.geoDescription = cequintCallerIdContact.geoDescription; 642 info.sourceType = ContactSource.Type.SOURCE_TYPE_CEQUINT_CALLER_ID; 643 } 644 // Only update photo if local lookup has no result. 645 if (!info.contactExists && info.photoUri == null && cequintCallerIdContact.imageUrl != null) { 646 info.photoUri = UriUtils.parseUriOrNull(cequintCallerIdContact.imageUrl); 647 } 648 } 649} 650