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