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 lookupNumber(String number, String countryIso)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") lookupNumber(String number, String countryIso, long directoryId)224 public ContactInfo lookupNumber(String number, String countryIso, long directoryId) { 225 if (TextUtils.isEmpty(number)) { 226 return null; 227 } 228 229 ContactInfo info; 230 231 if (PhoneNumberHelper.isUriNumber(number)) { 232 // The number is a SIP address.. 233 info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId)); 234 if (info == null || info == ContactInfo.EMPTY) { 235 // If lookup failed, check if the "username" of the SIP address is a phone number. 236 String username = PhoneNumberHelper.getUsernameFromUriNumber(number); 237 if (PhoneNumberUtils.isGlobalPhoneNumber(username)) { 238 info = queryContactInfoForPhoneNumber(username, countryIso, directoryId); 239 } 240 } 241 } else { 242 // Look for a contact that has the given phone number. 243 info = queryContactInfoForPhoneNumber(number, countryIso, directoryId); 244 } 245 246 final ContactInfo updatedInfo; 247 if (info == null) { 248 // The lookup failed. 249 updatedInfo = null; 250 } else { 251 // If we did not find a matching contact, generate an empty contact info for the number. 252 if (info == ContactInfo.EMPTY) { 253 // Did not find a matching contact. 254 updatedInfo = createEmptyContactInfoForNumber(number, countryIso); 255 } else { 256 updatedInfo = info; 257 } 258 } 259 return updatedInfo; 260 } 261 createEmptyContactInfoForNumber(String number, String countryIso)262 private ContactInfo createEmptyContactInfoForNumber(String number, String countryIso) { 263 ContactInfo contactInfo = new ContactInfo(); 264 contactInfo.number = number; 265 contactInfo.formattedNumber = formatPhoneNumber(number, null, countryIso); 266 contactInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); 267 contactInfo.lookupUri = createTemporaryContactUri(contactInfo.formattedNumber); 268 return contactInfo; 269 } 270 271 /** 272 * Return the contact info object if the remote directory lookup succeeds, otherwise return an 273 * empty contact info for the number. 274 */ lookupNumberInRemoteDirectory(String number, String countryIso)275 public ContactInfo lookupNumberInRemoteDirectory(String number, String countryIso) { 276 if (mCachedNumberLookupService != null) { 277 List<Long> remoteDirectories = getRemoteDirectories(mContext); 278 for (long directoryId : remoteDirectories) { 279 ContactInfo contactInfo = lookupNumber(number, countryIso, directoryId); 280 if (hasName(contactInfo)) { 281 return contactInfo; 282 } 283 } 284 } 285 return createEmptyContactInfoForNumber(number, countryIso); 286 } 287 hasName(ContactInfo contactInfo)288 public boolean hasName(ContactInfo contactInfo) { 289 return contactInfo != null && !TextUtils.isEmpty(contactInfo.name); 290 } 291 getRemoteDirectories(Context context)292 private List<Long> getRemoteDirectories(Context context) { 293 List<Long> remoteDirectories = new ArrayList<>(); 294 Uri uri = 295 VERSION.SDK_INT >= VERSION_CODES.N 296 ? Directory.ENTERPRISE_CONTENT_URI 297 : Directory.CONTENT_URI; 298 ContentResolver cr = context.getContentResolver(); 299 Cursor cursor = cr.query(uri, new String[] {Directory._ID}, null, null, null); 300 int idIndex = cursor.getColumnIndex(Directory._ID); 301 if (cursor == null) { 302 return remoteDirectories; 303 } 304 try { 305 while (cursor.moveToNext()) { 306 long directoryId = cursor.getLong(idIndex); 307 if (DirectoryCompat.isRemoteDirectoryId(directoryId)) { 308 remoteDirectories.add(directoryId); 309 } 310 } 311 } finally { 312 cursor.close(); 313 } 314 return remoteDirectories; 315 } 316 317 /** 318 * Looks up a contact using the given URI. 319 * 320 * <p>It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is 321 * found, or the {@link ContactInfo} for the given contact. 322 * 323 * <p>The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned 324 * value. 325 */ lookupContactFromUri(Uri uri)326 ContactInfo lookupContactFromUri(Uri uri) { 327 if (uri == null) { 328 return null; 329 } 330 if (!PermissionsUtil.hasContactsReadPermissions(mContext)) { 331 return ContactInfo.EMPTY; 332 } 333 334 Cursor phoneLookupCursor = null; 335 try { 336 String[] projection = PhoneQuery.getPhoneLookupProjection(uri); 337 phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, null); 338 } catch (NullPointerException e) { 339 // Trap NPE from pre-N CP2 340 return null; 341 } 342 if (phoneLookupCursor == null) { 343 return null; 344 } 345 346 try { 347 if (!phoneLookupCursor.moveToFirst()) { 348 return ContactInfo.EMPTY; 349 } 350 String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY); 351 ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey); 352 fillAdditionalContactInfo(mContext, contactInfo); 353 return contactInfo; 354 } finally { 355 phoneLookupCursor.close(); 356 } 357 } 358 createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey)359 private ContactInfo createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey) { 360 ContactInfo info = new ContactInfo(); 361 info.lookupKey = lookupKey; 362 info.lookupUri = 363 Contacts.getLookupUri(phoneLookupCursor.getLong(PhoneQuery.PERSON_ID), lookupKey); 364 info.name = phoneLookupCursor.getString(PhoneQuery.NAME); 365 info.type = phoneLookupCursor.getInt(PhoneQuery.PHONE_TYPE); 366 info.label = phoneLookupCursor.getString(PhoneQuery.LABEL); 367 info.number = phoneLookupCursor.getString(PhoneQuery.MATCHED_NUMBER); 368 info.normalizedNumber = phoneLookupCursor.getString(PhoneQuery.NORMALIZED_NUMBER); 369 info.photoId = phoneLookupCursor.getLong(PhoneQuery.PHOTO_ID); 370 info.photoUri = UriUtils.parseUriOrNull(phoneLookupCursor.getString(PhoneQuery.PHOTO_URI)); 371 info.formattedNumber = null; 372 info.userType = 373 ContactsUtils.determineUserType(null, phoneLookupCursor.getLong(PhoneQuery.PERSON_ID)); 374 info.contactExists = true; 375 376 return info; 377 } 378 fillAdditionalContactInfo(Context context, ContactInfo contactInfo)379 private void fillAdditionalContactInfo(Context context, ContactInfo contactInfo) { 380 if (contactInfo.number == null) { 381 return; 382 } 383 Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(contactInfo.number)); 384 try (Cursor cursor = 385 context 386 .getContentResolver() 387 .query(uri, PhoneQuery.ADDITIONAL_CONTACT_INFO_PROJECTION, null, null, null)) { 388 if (cursor == null || !cursor.moveToFirst()) { 389 return; 390 } 391 contactInfo.nameAlternative = 392 cursor.getString(PhoneQuery.ADDITIONAL_CONTACT_INFO_DISPLAY_NAME_ALTERNATIVE); 393 contactInfo.carrierPresence = 394 cursor.getInt(PhoneQuery.ADDITIONAL_CONTACT_INFO_CARRIER_PRESENCE); 395 } 396 } 397 398 /** 399 * Determines the contact information for the given phone number. 400 * 401 * <p>It returns the contact info if found. 402 * 403 * <p>If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}. 404 * 405 * <p>If the lookup fails for some other reason, it returns null. 406 */ 407 @SuppressWarnings("ReferenceEquality") queryContactInfoForPhoneNumber( String number, String countryIso, long directoryId)408 private ContactInfo queryContactInfoForPhoneNumber( 409 String number, String countryIso, long directoryId) { 410 if (TextUtils.isEmpty(number)) { 411 return null; 412 } 413 414 ContactInfo info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId)); 415 if (info != null && info != ContactInfo.EMPTY) { 416 info.formattedNumber = formatPhoneNumber(number, null, countryIso); 417 } else if (mCachedNumberLookupService != null) { 418 CachedContactInfo cacheInfo = 419 mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number); 420 if (cacheInfo != null) { 421 info = cacheInfo.getContactInfo().isBadData ? null : cacheInfo.getContactInfo(); 422 } else { 423 info = null; 424 } 425 } 426 return info; 427 } 428 429 /** 430 * Format the given phone number 431 * 432 * @param number the number to be formatted. 433 * @param normalizedNumber the normalized number of the given number. 434 * @param countryIso the ISO 3166-1 two letters country code, the country's convention will be 435 * used to format the number if the normalized phone is null. 436 * @return the formatted number, or the given number if it was formatted. 437 */ formatPhoneNumber(String number, String normalizedNumber, String countryIso)438 private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) { 439 if (TextUtils.isEmpty(number)) { 440 return ""; 441 } 442 // If "number" is really a SIP address, don't try to do any formatting at all. 443 if (PhoneNumberHelper.isUriNumber(number)) { 444 return number; 445 } 446 if (TextUtils.isEmpty(countryIso)) { 447 countryIso = mCurrentCountryIso; 448 } 449 return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso); 450 } 451 452 /** 453 * Stores differences between the updated contact info and the current call log contact info. 454 * 455 * @param number The number of the contact. 456 * @param countryIso The country associated with this number. 457 * @param updatedInfo The updated contact info. 458 * @param callLogInfo The call log entry's current contact info. 459 */ updateCallLogContactInfo( String number, String countryIso, ContactInfo updatedInfo, ContactInfo callLogInfo)460 public void updateCallLogContactInfo( 461 String number, String countryIso, ContactInfo updatedInfo, ContactInfo callLogInfo) { 462 if (!PermissionsUtil.hasPermission(mContext, android.Manifest.permission.WRITE_CALL_LOG)) { 463 return; 464 } 465 466 final ContentValues values = new ContentValues(); 467 boolean needsUpdate = false; 468 469 if (callLogInfo != null) { 470 if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) { 471 values.put(Calls.CACHED_NAME, updatedInfo.name); 472 needsUpdate = true; 473 } 474 475 if (updatedInfo.type != callLogInfo.type) { 476 values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type); 477 needsUpdate = true; 478 } 479 480 if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) { 481 values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label); 482 needsUpdate = true; 483 } 484 485 if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) { 486 values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri)); 487 needsUpdate = true; 488 } 489 490 // Only replace the normalized number if the new updated normalized number isn't empty. 491 if (!TextUtils.isEmpty(updatedInfo.normalizedNumber) 492 && !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) { 493 values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber); 494 needsUpdate = true; 495 } 496 497 if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) { 498 values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number); 499 needsUpdate = true; 500 } 501 502 if (updatedInfo.photoId != callLogInfo.photoId) { 503 values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId); 504 needsUpdate = true; 505 } 506 507 final Uri updatedPhotoUriContactsOnly = UriUtils.nullForNonContactsUri(updatedInfo.photoUri); 508 if (!UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) { 509 values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(updatedPhotoUriContactsOnly)); 510 needsUpdate = true; 511 } 512 513 if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) { 514 values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber); 515 needsUpdate = true; 516 } 517 518 if (!TextUtils.equals(updatedInfo.geoDescription, callLogInfo.geoDescription)) { 519 values.put(Calls.GEOCODED_LOCATION, updatedInfo.geoDescription); 520 needsUpdate = true; 521 } 522 } else { 523 // No previous values, store all of them. 524 values.put(Calls.CACHED_NAME, updatedInfo.name); 525 values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type); 526 values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label); 527 values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri)); 528 values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number); 529 values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber); 530 values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId); 531 values.put( 532 Calls.CACHED_PHOTO_URI, 533 UriUtils.uriToString(UriUtils.nullForNonContactsUri(updatedInfo.photoUri))); 534 values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber); 535 values.put(Calls.GEOCODED_LOCATION, updatedInfo.geoDescription); 536 needsUpdate = true; 537 } 538 539 if (!needsUpdate) { 540 return; 541 } 542 543 try { 544 if (countryIso == null) { 545 mContext 546 .getContentResolver() 547 .update( 548 TelecomUtil.getCallLogUri(mContext), 549 values, 550 Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL", 551 new String[] {number}); 552 } else { 553 mContext 554 .getContentResolver() 555 .update( 556 TelecomUtil.getCallLogUri(mContext), 557 values, 558 Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?", 559 new String[] {number, countryIso}); 560 } 561 } catch (SQLiteFullException e) { 562 LogUtil.e(TAG, "Unable to update contact info in call log db", e); 563 } 564 } 565 updateCachedNumberLookupService(ContactInfo updatedInfo)566 public void updateCachedNumberLookupService(ContactInfo updatedInfo) { 567 if (mCachedNumberLookupService != null) { 568 if (hasName(updatedInfo)) { 569 CachedContactInfo cachedContactInfo = 570 mCachedNumberLookupService.buildCachedContactInfo(updatedInfo); 571 mCachedNumberLookupService.addContact(mContext, cachedContactInfo); 572 } 573 } 574 } 575 576 /** 577 * Given a contact's sourceType, return true if the contact is a business 578 * 579 * @param sourceType sourceType of the contact. This is usually populated by {@link 580 * #mCachedNumberLookupService}. 581 */ isBusiness(ContactSource.Type sourceType)582 public boolean isBusiness(ContactSource.Type sourceType) { 583 return mCachedNumberLookupService != null && mCachedNumberLookupService.isBusiness(sourceType); 584 } 585 586 /** 587 * This function looks at a contact's source and determines if the user can mark caller ids from 588 * this source as invalid. 589 * 590 * @param sourceType The source type to be checked 591 * @param objectId The ID of the Contact object. 592 * @return true if contacts from this source can be marked with an invalid caller id 593 */ canReportAsInvalid(ContactSource.Type sourceType, String objectId)594 public boolean canReportAsInvalid(ContactSource.Type sourceType, String objectId) { 595 return mCachedNumberLookupService != null 596 && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId); 597 } 598 599 /** 600 * Update ContactInfo by querying to Cequint Caller ID. Only name, geoDescription and photo uri 601 * will be updated if available. 602 */ 603 @WorkerThread updateFromCequintCallerId(ContactInfo info, String number)604 public void updateFromCequintCallerId(ContactInfo info, String number) { 605 Assert.isWorkerThread(); 606 if (!CequintCallerIdManager.isCequintCallerIdEnabled(mContext)) { 607 return; 608 } 609 CequintCallerIdContact cequintCallerIdContact = 610 CequintCallerIdManager.getCequintCallerIdContact(mContext, number); 611 if (cequintCallerIdContact == null) { 612 return; 613 } 614 if (TextUtils.isEmpty(info.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) { 615 info.name = cequintCallerIdContact.name; 616 } 617 if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) { 618 info.geoDescription = cequintCallerIdContact.geoDescription; 619 info.sourceType = ContactSource.Type.SOURCE_TYPE_CEQUINT_CALLER_ID; 620 } 621 // Only update photo if local lookup has no result. 622 if (!info.contactExists && info.photoUri == null && cequintCallerIdContact.imageUrl != null) { 623 info.photoUri = UriUtils.parseUriOrNull(cequintCallerIdContact.imageUrl); 624 } 625 } 626 } 627