summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/phonenumbercache
diff options
context:
space:
mode:
authorEric Erfanian <erfanian@google.com>2017-02-22 16:32:36 -0800
committerEric Erfanian <erfanian@google.com>2017-03-01 09:56:52 -0800
commitccca31529c07970e89419fb85a9e8153a5396838 (patch)
treea7034c0a01672b97728c13282a2672771cd28baa /java/com/android/dialer/phonenumbercache
parente7ae4624ba6f25cb8e648db74e0d64c0113a16ba (diff)
Update dialer sources.
Test: Built package and system image. This change clobbers the old source, and is an export from an internal Google repository. The internal repository was forked form Android in March, and this change includes modifications since then, to near the v8 release. Since the fork, we've moved code from monolithic to independent modules. In addition, we've switched to Blaze/Bazel as the build sysetm. This export, however, still uses make. New dependencies have been added: - Dagger - Auto-Value - Glide - Libshortcutbadger Going forward, development will still be in Google3, and the Gerrit release will become an automated export, with the next drop happening in ~ two weeks. Android.mk includes local modifications from ToT. Abridged changelog: Bug fixes ● Not able to mute, add a call when using Phone app in multiwindow mode ● Double tap on keypad triggering multiple key and tones ● Reported spam numbers not showing as spam in the call log ● Crash when user tries to block number while Phone app is not set as default ● Crash when user picks a number from search auto-complete list Visual Voicemail (VVM) improvements ● Share Voicemail audio via standard exporting mechanisms that support file attachment (email, MMS, etc.) ● Make phone number, email and web sites in VVM transcript clickable ● Set PIN before declining VVM Terms of Service {Carrier} ● Set client type for outbound visual voicemail SMS {Carrier} New incoming call and incall UI on older devices (Android M) ● Updated Phone app icon ● New incall UI (large buttons, button labels) ● New and animated Answer/Reject gestures Accessibility ● Add custom answer/decline call buttons on answer screen for touch exploration accessibility services ● Increase size of touch target ● Add verbal feedback when a Voicemail fails to load ● Fix pressing of Phone buttons while in a phone call using Switch Access ● Fix selecting and opening contacts in talkback mode ● Split focus for ‘Learn More’ link in caller id & spam to help distinguish similar text Other ● Backup & Restore for App Preferences ● Prompt user to enable Wi-Fi calling if the call ends due to out of service and Wi-Fi is connected ● Rename “Dialpad” to “Keypad” ● Show "Private number" for restricted calls ● Delete unused items (vcard, add contact, call history) from Phone menu Change-Id: I2a7e53532a24c21bf308bf0a6d178d7ddbca4958
Diffstat (limited to 'java/com/android/dialer/phonenumbercache')
-rw-r--r--java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java77
-rw-r--r--java/com/android/dialer/phonenumbercache/CallLogQuery.java107
-rw-r--r--java/com/android/dialer/phonenumbercache/ContactInfo.java165
-rw-r--r--java/com/android/dialer/phonenumbercache/ContactInfoHelper.java586
-rw-r--r--java/com/android/dialer/phonenumbercache/PhoneLookupUtil.java40
-rw-r--r--java/com/android/dialer/phonenumbercache/PhoneNumberCache.java50
-rw-r--r--java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindings.java26
-rw-r--r--java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsFactory.java26
-rw-r--r--java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsStub.java29
-rw-r--r--java/com/android/dialer/phonenumbercache/PhoneQuery.java96
10 files changed, 1202 insertions, 0 deletions
diff --git a/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java b/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java
new file mode 100644
index 000000000..03b77b91c
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import java.io.InputStream;
+
+public interface CachedNumberLookupService {
+
+ CachedContactInfo buildCachedContactInfo(ContactInfo info);
+
+ /**
+ * Perform a lookup using the cached number lookup service to return contact information stored in
+ * the cache that corresponds to the given number.
+ *
+ * @param context Valid context
+ * @param number Phone number to lookup the cache for
+ * @return A {@link CachedContactInfo} containing the contact information if the phone number is
+ * found in the cache, {@link ContactInfo#EMPTY} if the phone number was not found in the
+ * cache, and null if there was an error when querying the cache.
+ */
+ CachedContactInfo lookupCachedContactFromNumber(Context context, String number);
+
+ void addContact(Context context, CachedContactInfo info);
+
+ boolean isCacheUri(String uri);
+
+ boolean isBusiness(int sourceType);
+
+ boolean canReportAsInvalid(int sourceType, String objectId);
+
+ /** @return return {@link Uri} to the photo or return {@code null} when failing to add photo */
+ @Nullable
+ Uri addPhoto(Context context, String number, InputStream in);
+
+ /**
+ * Remove all cached phone number entries from the cache, regardless of how old they are.
+ *
+ * @param context Valid context
+ */
+ void clearAllCacheEntries(Context context);
+
+ interface CachedContactInfo {
+
+ int SOURCE_TYPE_DIRECTORY = 1;
+ int SOURCE_TYPE_EXTENDED = 2;
+ int SOURCE_TYPE_PLACES = 3;
+ int SOURCE_TYPE_PROFILE = 4;
+ int SOURCE_TYPE_CNAP = 5;
+
+ ContactInfo getContactInfo();
+
+ void setSource(int sourceType, String name, long directoryId);
+
+ void setDirectorySource(String name, long directoryId);
+
+ void setExtendedSource(String name, long directoryId);
+
+ void setLookupKey(String lookupKey);
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/CallLogQuery.java b/java/com/android/dialer/phonenumbercache/CallLogQuery.java
new file mode 100644
index 000000000..6d4756927
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/CallLogQuery.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** The query for the call log table. */
+public final class CallLogQuery {
+
+ public static final int ID = 0;
+ public static final int NUMBER = 1;
+ public static final int DATE = 2;
+ public static final int DURATION = 3;
+ public static final int CALL_TYPE = 4;
+ public static final int COUNTRY_ISO = 5;
+ public static final int VOICEMAIL_URI = 6;
+ public static final int GEOCODED_LOCATION = 7;
+ public static final int CACHED_NAME = 8;
+ public static final int CACHED_NUMBER_TYPE = 9;
+ public static final int CACHED_NUMBER_LABEL = 10;
+ public static final int CACHED_LOOKUP_URI = 11;
+ public static final int CACHED_MATCHED_NUMBER = 12;
+ public static final int CACHED_NORMALIZED_NUMBER = 13;
+ public static final int CACHED_PHOTO_ID = 14;
+ public static final int CACHED_FORMATTED_NUMBER = 15;
+ public static final int IS_READ = 16;
+ public static final int NUMBER_PRESENTATION = 17;
+ public static final int ACCOUNT_COMPONENT_NAME = 18;
+ public static final int ACCOUNT_ID = 19;
+ public static final int FEATURES = 20;
+ public static final int DATA_USAGE = 21;
+ public static final int TRANSCRIPTION = 22;
+ public static final int CACHED_PHOTO_URI = 23;
+
+ @RequiresApi(VERSION_CODES.N)
+ public static final int POST_DIAL_DIGITS = 24;
+
+ @RequiresApi(VERSION_CODES.N)
+ public static final int VIA_NUMBER = 25;
+
+ private static final String[] PROJECTION_M =
+ new String[] {
+ Calls._ID, // 0
+ Calls.NUMBER, // 1
+ Calls.DATE, // 2
+ Calls.DURATION, // 3
+ Calls.TYPE, // 4
+ Calls.COUNTRY_ISO, // 5
+ Calls.VOICEMAIL_URI, // 6
+ Calls.GEOCODED_LOCATION, // 7
+ Calls.CACHED_NAME, // 8
+ Calls.CACHED_NUMBER_TYPE, // 9
+ Calls.CACHED_NUMBER_LABEL, // 10
+ Calls.CACHED_LOOKUP_URI, // 11
+ Calls.CACHED_MATCHED_NUMBER, // 12
+ Calls.CACHED_NORMALIZED_NUMBER, // 13
+ Calls.CACHED_PHOTO_ID, // 14
+ Calls.CACHED_FORMATTED_NUMBER, // 15
+ Calls.IS_READ, // 16
+ Calls.NUMBER_PRESENTATION, // 17
+ Calls.PHONE_ACCOUNT_COMPONENT_NAME, // 18
+ Calls.PHONE_ACCOUNT_ID, // 19
+ Calls.FEATURES, // 20
+ Calls.DATA_USAGE, // 21
+ Calls.TRANSCRIPTION, // 22
+ Calls.CACHED_PHOTO_URI, // 23
+ };
+
+ private static final String[] PROJECTION_N;
+
+ static {
+ List<String> projectionList = new ArrayList<>(Arrays.asList(PROJECTION_M));
+ projectionList.add(CallLog.Calls.POST_DIAL_DIGITS);
+ projectionList.add(CallLog.Calls.VIA_NUMBER);
+ PROJECTION_N = projectionList.toArray(new String[projectionList.size()]);
+ }
+
+ @NonNull
+ public static String[] getProjection() {
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ return PROJECTION_N;
+ }
+ return PROJECTION_M;
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfo.java b/java/com/android/dialer/phonenumbercache/ContactInfo.java
new file mode 100644
index 000000000..d7a75c34f
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/ContactInfo.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import com.android.contacts.common.ContactsUtils.UserType;
+import com.android.contacts.common.util.UriUtils;
+
+/** Information for a contact as needed by the Call Log. */
+public class ContactInfo {
+
+ public static final ContactInfo EMPTY = new ContactInfo();
+ public Uri lookupUri;
+ /**
+ * Contact lookup key. Note this may be a lookup key for a corp contact, in which case "lookup by
+ * lookup key" doesn't work on the personal profile.
+ */
+ public String lookupKey;
+
+ public String name;
+ public String nameAlternative;
+ public int type;
+ public String label;
+ public String number;
+ public String formattedNumber;
+ /*
+ * ContactInfo.normalizedNumber is a column value returned by PhoneLookup query. By definition,
+ * it's E164 representation.
+ * http://developer.android.com/reference/android/provider/ContactsContract.PhoneLookupColumns.
+ * html#NORMALIZED_NUMBER.
+ *
+ * The fallback value, when PhoneLookup fails or else, should be either null or
+ * PhoneNumberUtils.formatNumberToE164.
+ */
+ public String normalizedNumber;
+ /** The photo for the contact, if available. */
+ public long photoId;
+ /** The high-res photo for the contact, if available. */
+ public Uri photoUri;
+
+ public boolean isBadData;
+ public String objectId;
+ public @UserType long userType;
+ public int sourceType = 0;
+
+ /** @see android.provider.ContactsContract.CommonDataKinds.Phone#CARRIER_PRESENCE */
+ public int carrierPresence;
+
+ @Override
+ public int hashCode() {
+ // Uses only name and contactUri to determine hashcode.
+ // This should be sufficient to have a reasonable distribution of hash codes.
+ // Moreover, there should be no two people with the same lookupUri.
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((lookupUri == null) ? 0 : lookupUri.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ContactInfo other = (ContactInfo) obj;
+ if (!UriUtils.areEqual(lookupUri, other.lookupUri)) {
+ return false;
+ }
+ if (!TextUtils.equals(name, other.name)) {
+ return false;
+ }
+ if (!TextUtils.equals(nameAlternative, other.nameAlternative)) {
+ return false;
+ }
+ if (type != other.type) {
+ return false;
+ }
+ if (!TextUtils.equals(label, other.label)) {
+ return false;
+ }
+ if (!TextUtils.equals(number, other.number)) {
+ return false;
+ }
+ if (!TextUtils.equals(formattedNumber, other.formattedNumber)) {
+ return false;
+ }
+ if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) {
+ return false;
+ }
+ if (photoId != other.photoId) {
+ return false;
+ }
+ if (!UriUtils.areEqual(photoUri, other.photoUri)) {
+ return false;
+ }
+ if (!TextUtils.equals(objectId, other.objectId)) {
+ return false;
+ }
+ if (userType != other.userType) {
+ return false;
+ }
+ return carrierPresence == other.carrierPresence;
+ }
+
+ @Override
+ public String toString() {
+ return "ContactInfo{"
+ + "lookupUri="
+ + lookupUri
+ + ", name='"
+ + name
+ + '\''
+ + ", nameAlternative='"
+ + nameAlternative
+ + '\''
+ + ", type="
+ + type
+ + ", label='"
+ + label
+ + '\''
+ + ", number='"
+ + number
+ + '\''
+ + ", formattedNumber='"
+ + formattedNumber
+ + '\''
+ + ", normalizedNumber='"
+ + normalizedNumber
+ + '\''
+ + ", photoId="
+ + photoId
+ + ", photoUri="
+ + photoUri
+ + ", objectId='"
+ + objectId
+ + '\''
+ + ", userType="
+ + userType
+ + ", carrierPresence="
+ + carrierPresence
+ + '}';
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
new file mode 100644
index 000000000..6a5e2e6b4
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteFullException;
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.PhoneLookup;
+import android.support.annotation.Nullable;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.contacts.common.ContactsUtils;
+import com.android.contacts.common.ContactsUtils.UserType;
+import com.android.contacts.common.compat.DirectoryCompat;
+import com.android.contacts.common.util.Constants;
+import com.android.contacts.common.util.UriUtils;
+import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
+import com.android.dialer.phonenumberutil.PhoneNumberHelper;
+import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.PermissionsUtil;
+import java.util.ArrayList;
+import java.util.List;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Utility class to look up the contact information for a given number. */
+// This class uses Java 7 language features, so it must target M+
+@TargetApi(VERSION_CODES.M)
+public class ContactInfoHelper {
+
+ private static final String TAG = ContactInfoHelper.class.getSimpleName();
+
+ private final Context mContext;
+ private final String mCurrentCountryIso;
+ private final CachedNumberLookupService mCachedNumberLookupService;
+
+ public ContactInfoHelper(Context context, String currentCountryIso) {
+ mContext = context;
+ mCurrentCountryIso = currentCountryIso;
+ mCachedNumberLookupService = PhoneNumberCache.get(mContext).getCachedNumberLookupService();
+ }
+
+ /**
+ * Creates a JSON-encoded lookup uri for a unknown number without an associated contact
+ *
+ * @param number - Unknown phone number
+ * @return JSON-encoded URI that can be used to perform a lookup when clicking on the quick
+ * contact card.
+ */
+ private static Uri createTemporaryContactUri(String number) {
+ try {
+ final JSONObject contactRows =
+ new JSONObject()
+ .put(
+ Phone.CONTENT_ITEM_TYPE,
+ new JSONObject().put(Phone.NUMBER, number).put(Phone.TYPE, Phone.TYPE_CUSTOM));
+
+ final String jsonString =
+ new JSONObject()
+ .put(Contacts.DISPLAY_NAME, number)
+ .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.PHONE)
+ .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
+ .toString();
+
+ return Contacts.CONTENT_LOOKUP_URI
+ .buildUpon()
+ .appendPath(Constants.LOOKUP_URI_ENCODED)
+ .appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Long.MAX_VALUE))
+ .encodedFragment(jsonString)
+ .build();
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ public static String lookUpDisplayNameAlternative(
+ Context context, String lookupKey, @UserType long userType, @Nullable Long directoryId) {
+ // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
+ if (lookupKey == null || userType == ContactsUtils.USER_TYPE_WORK) {
+ return null;
+ }
+
+ if (directoryId != null) {
+ // Query {@link Contacts#CONTENT_LOOKUP_URI} with work lookup key is not allowed.
+ if (DirectoryCompat.isEnterpriseDirectoryId(directoryId)) {
+ return null;
+ }
+
+ // Skip this to avoid an extra remote network call for alternative name
+ if (DirectoryCompat.isRemoteDirectoryId(directoryId)) {
+ return null;
+ }
+ }
+
+ final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
+ Cursor cursor = null;
+ try {
+ cursor =
+ context
+ .getContentResolver()
+ .query(uri, PhoneQuery.DISPLAY_NAME_ALTERNATIVE_PROJECTION, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getString(PhoneQuery.NAME_ALTERNATIVE);
+ }
+ } catch (IllegalArgumentException e) {
+ // Avoid dialer crash when lookup key is not valid
+ Log.e(TAG, "IllegalArgumentException in lookUpDisplayNameAlternative", e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return null;
+ }
+
+ public static Uri getContactInfoLookupUri(String number) {
+ return getContactInfoLookupUri(number, -1);
+ }
+
+ public static Uri getContactInfoLookupUri(String number, long directoryId) {
+ // Get URI for the number in the PhoneLookup table, with a parameter to indicate whether
+ // the number is a SIP number.
+ Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
+ if (VERSION.SDK_INT < VERSION_CODES.N) {
+ if (directoryId != -1) {
+ // ENTERPRISE_CONTENT_FILTER_URI in M doesn't support directory lookup
+ uri = PhoneLookup.CONTENT_FILTER_URI;
+ } else {
+ // b/25900607 in M. PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, encodes twice.
+ number = Uri.encode(number);
+ }
+ }
+ Uri.Builder builder =
+ uri.buildUpon()
+ .appendPath(number)
+ .appendQueryParameter(
+ PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
+ String.valueOf(PhoneNumberHelper.isUriNumber(number)));
+ if (directoryId != -1) {
+ builder.appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns the contact information stored in an entry of the call log.
+ *
+ * @param c A cursor pointing to an entry in the call log.
+ */
+ public static ContactInfo getContactInfo(Cursor c) {
+ ContactInfo info = new ContactInfo();
+ info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI));
+ info.name = c.getString(CallLogQuery.CACHED_NAME);
+ info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
+ info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
+ String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER);
+ String postDialDigits =
+ (VERSION.SDK_INT >= VERSION_CODES.N) ? c.getString(CallLogQuery.POST_DIAL_DIGITS) : "";
+ info.number =
+ (matchedNumber == null) ? c.getString(CallLogQuery.NUMBER) + postDialDigits : matchedNumber;
+
+ info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
+ info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
+ info.photoUri =
+ UriUtils.nullForNonContactsUri(
+ UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_PHOTO_URI)));
+ info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
+
+ return info;
+ }
+
+ public ContactInfo lookupNumber(String number, String countryIso) {
+ return lookupNumber(number, countryIso, -1);
+ }
+
+ /**
+ * Returns the contact information for the given number.
+ *
+ * <p>If the number does not match any contact, returns a contact info containing only the number
+ * and the formatted number.
+ *
+ * <p>If an error occurs during the lookup, it returns null.
+ *
+ * @param number the number to look up
+ * @param countryIso the country associated with this number
+ * @param directoryId the id of the directory to lookup
+ */
+ @Nullable
+ @SuppressWarnings("ReferenceEquality")
+ public ContactInfo lookupNumber(String number, String countryIso, long directoryId) {
+ if (TextUtils.isEmpty(number)) {
+ return null;
+ }
+
+ ContactInfo info;
+
+ if (PhoneNumberHelper.isUriNumber(number)) {
+ // The number is a SIP address..
+ info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId));
+ if (info == null || info == ContactInfo.EMPTY) {
+ // If lookup failed, check if the "username" of the SIP address is a phone number.
+ String username = PhoneNumberHelper.getUsernameFromUriNumber(number);
+ if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
+ info = queryContactInfoForPhoneNumber(username, countryIso, directoryId);
+ }
+ }
+ } else {
+ // Look for a contact that has the given phone number.
+ info = queryContactInfoForPhoneNumber(number, countryIso, directoryId);
+ }
+
+ final ContactInfo updatedInfo;
+ if (info == null) {
+ // The lookup failed.
+ updatedInfo = null;
+ } else {
+ // If we did not find a matching contact, generate an empty contact info for the number.
+ if (info == ContactInfo.EMPTY) {
+ // Did not find a matching contact.
+ updatedInfo = createEmptyContactInfoForNumber(number, countryIso);
+ } else {
+ updatedInfo = info;
+ }
+ }
+ return updatedInfo;
+ }
+
+ private ContactInfo createEmptyContactInfoForNumber(String number, String countryIso) {
+ ContactInfo contactInfo = new ContactInfo();
+ contactInfo.number = number;
+ contactInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
+ contactInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+ contactInfo.lookupUri = createTemporaryContactUri(contactInfo.formattedNumber);
+ return contactInfo;
+ }
+
+ /**
+ * Return the contact info object if the remote directory lookup succeeds, otherwise return an
+ * empty contact info for the number.
+ */
+ public ContactInfo lookupNumberInRemoteDirectory(String number, String countryIso) {
+ if (mCachedNumberLookupService != null) {
+ List<Long> remoteDirectories = getRemoteDirectories(mContext);
+ for (long directoryId : remoteDirectories) {
+ ContactInfo contactInfo = lookupNumber(number, countryIso, directoryId);
+ if (hasName(contactInfo)) {
+ return contactInfo;
+ }
+ }
+ }
+ return createEmptyContactInfoForNumber(number, countryIso);
+ }
+
+ public boolean hasName(ContactInfo contactInfo) {
+ return contactInfo != null && !TextUtils.isEmpty(contactInfo.name);
+ }
+
+ private List<Long> getRemoteDirectories(Context context) {
+ List<Long> remoteDirectories = new ArrayList<>();
+ Uri uri =
+ VERSION.SDK_INT >= VERSION_CODES.N
+ ? Directory.ENTERPRISE_CONTENT_URI
+ : Directory.CONTENT_URI;
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(uri, new String[] {Directory._ID}, null, null, null);
+ int idIndex = cursor.getColumnIndex(Directory._ID);
+ if (cursor == null) {
+ return remoteDirectories;
+ }
+ try {
+ while (cursor.moveToNext()) {
+ long directoryId = cursor.getLong(idIndex);
+ if (DirectoryCompat.isRemoteDirectoryId(directoryId)) {
+ remoteDirectories.add(directoryId);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ return remoteDirectories;
+ }
+
+ /**
+ * Looks up a contact using the given URI.
+ *
+ * <p>It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is
+ * found, or the {@link ContactInfo} for the given contact.
+ *
+ * <p>The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned
+ * value.
+ */
+ ContactInfo lookupContactFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ if (!PermissionsUtil.hasContactsPermissions(mContext)) {
+ return ContactInfo.EMPTY;
+ }
+
+ Cursor phoneLookupCursor = null;
+ try {
+ String[] projection = PhoneQuery.getPhoneLookupProjection(uri);
+ phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, null);
+ } catch (NullPointerException e) {
+ // Trap NPE from pre-N CP2
+ return null;
+ }
+ if (phoneLookupCursor == null) {
+ return null;
+ }
+
+ try {
+ if (!phoneLookupCursor.moveToFirst()) {
+ return ContactInfo.EMPTY;
+ }
+ String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY);
+ ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey);
+ fillAdditionalContactInfo(mContext, contactInfo);
+ return contactInfo;
+ } finally {
+ phoneLookupCursor.close();
+ }
+ }
+
+ private ContactInfo createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey) {
+ ContactInfo info = new ContactInfo();
+ info.lookupKey = lookupKey;
+ info.lookupUri =
+ Contacts.getLookupUri(phoneLookupCursor.getLong(PhoneQuery.PERSON_ID), lookupKey);
+ info.name = phoneLookupCursor.getString(PhoneQuery.NAME);
+ info.type = phoneLookupCursor.getInt(PhoneQuery.PHONE_TYPE);
+ info.label = phoneLookupCursor.getString(PhoneQuery.LABEL);
+ info.number = phoneLookupCursor.getString(PhoneQuery.MATCHED_NUMBER);
+ info.normalizedNumber = phoneLookupCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
+ info.photoId = phoneLookupCursor.getLong(PhoneQuery.PHOTO_ID);
+ info.photoUri = UriUtils.parseUriOrNull(phoneLookupCursor.getString(PhoneQuery.PHOTO_URI));
+ info.formattedNumber = null;
+ info.userType =
+ ContactsUtils.determineUserType(null, phoneLookupCursor.getLong(PhoneQuery.PERSON_ID));
+
+ return info;
+ }
+
+ private void fillAdditionalContactInfo(Context context, ContactInfo contactInfo) {
+ if (contactInfo.number == null) {
+ return;
+ }
+ Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(contactInfo.number));
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(uri, PhoneQuery.ADDITIONAL_CONTACT_INFO_PROJECTION, null, null, null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ return;
+ }
+ contactInfo.nameAlternative =
+ cursor.getString(PhoneQuery.ADDITIONAL_CONTACT_INFO_DISPLAY_NAME_ALTERNATIVE);
+ contactInfo.carrierPresence =
+ cursor.getInt(PhoneQuery.ADDITIONAL_CONTACT_INFO_CARRIER_PRESENCE);
+ }
+ }
+
+ /**
+ * Determines the contact information for the given phone number.
+ *
+ * <p>It returns the contact info if found.
+ *
+ * <p>If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
+ *
+ * <p>If the lookup fails for some other reason, it returns null.
+ */
+ @SuppressWarnings("ReferenceEquality")
+ private ContactInfo queryContactInfoForPhoneNumber(
+ String number, String countryIso, long directoryId) {
+ if (TextUtils.isEmpty(number)) {
+ return null;
+ }
+
+ ContactInfo info = lookupContactFromUri(getContactInfoLookupUri(number, directoryId));
+ if (info != null && info != ContactInfo.EMPTY) {
+ info.formattedNumber = formatPhoneNumber(number, null, countryIso);
+ } else if (mCachedNumberLookupService != null) {
+ CachedContactInfo cacheInfo =
+ mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number);
+ if (cacheInfo != null) {
+ info = cacheInfo.getContactInfo().isBadData ? null : cacheInfo.getContactInfo();
+ } else {
+ info = null;
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Format the given phone number
+ *
+ * @param number the number to be formatted.
+ * @param normalizedNumber the normalized number of the given number.
+ * @param countryIso the ISO 3166-1 two letters country code, the country's convention will be
+ * used to format the number if the normalized phone is null.
+ * @return the formatted number, or the given number if it was formatted.
+ */
+ private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) {
+ if (TextUtils.isEmpty(number)) {
+ return "";
+ }
+ // If "number" is really a SIP address, don't try to do any formatting at all.
+ if (PhoneNumberHelper.isUriNumber(number)) {
+ return number;
+ }
+ if (TextUtils.isEmpty(countryIso)) {
+ countryIso = mCurrentCountryIso;
+ }
+ return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
+ }
+
+ /**
+ * Stores differences between the updated contact info and the current call log contact info.
+ *
+ * @param number The number of the contact.
+ * @param countryIso The country associated with this number.
+ * @param updatedInfo The updated contact info.
+ * @param callLogInfo The call log entry's current contact info.
+ */
+ public void updateCallLogContactInfo(
+ String number, String countryIso, ContactInfo updatedInfo, ContactInfo callLogInfo) {
+ if (!PermissionsUtil.hasPermission(mContext, android.Manifest.permission.WRITE_CALL_LOG)) {
+ return;
+ }
+
+ final ContentValues values = new ContentValues();
+ boolean needsUpdate = false;
+
+ if (callLogInfo != null) {
+ if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
+ values.put(Calls.CACHED_NAME, updatedInfo.name);
+ needsUpdate = true;
+ }
+
+ if (updatedInfo.type != callLogInfo.type) {
+ values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
+ values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
+ needsUpdate = true;
+ }
+
+ if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) {
+ values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
+ needsUpdate = true;
+ }
+
+ // Only replace the normalized number if the new updated normalized number isn't empty.
+ if (!TextUtils.isEmpty(updatedInfo.normalizedNumber)
+ && !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) {
+ values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) {
+ values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
+ needsUpdate = true;
+ }
+
+ if (updatedInfo.photoId != callLogInfo.photoId) {
+ values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
+ needsUpdate = true;
+ }
+
+ final Uri updatedPhotoUriContactsOnly = UriUtils.nullForNonContactsUri(updatedInfo.photoUri);
+ if (!UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) {
+ values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(updatedPhotoUriContactsOnly));
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) {
+ values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
+ needsUpdate = true;
+ }
+ } else {
+ // No previous values, store all of them.
+ values.put(Calls.CACHED_NAME, updatedInfo.name);
+ values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
+ values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
+ values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
+ values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
+ values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
+ values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
+ values.put(
+ Calls.CACHED_PHOTO_URI,
+ UriUtils.uriToString(UriUtils.nullForNonContactsUri(updatedInfo.photoUri)));
+ values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
+ needsUpdate = true;
+ }
+
+ if (!needsUpdate) {
+ return;
+ }
+
+ try {
+ if (countryIso == null) {
+ mContext
+ .getContentResolver()
+ .update(
+ TelecomUtil.getCallLogUri(mContext),
+ values,
+ Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL",
+ new String[] {number});
+ } else {
+ mContext
+ .getContentResolver()
+ .update(
+ TelecomUtil.getCallLogUri(mContext),
+ values,
+ Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?",
+ new String[] {number, countryIso});
+ }
+ } catch (SQLiteFullException e) {
+ Log.e(TAG, "Unable to update contact info in call log db", e);
+ }
+ }
+
+ public void updateCachedNumberLookupService(ContactInfo updatedInfo) {
+ if (mCachedNumberLookupService != null) {
+ if (hasName(updatedInfo)) {
+ CachedContactInfo cachedContactInfo =
+ mCachedNumberLookupService.buildCachedContactInfo(updatedInfo);
+ mCachedNumberLookupService.addContact(mContext, cachedContactInfo);
+ }
+ }
+ }
+
+ /**
+ * Given a contact's sourceType, return true if the contact is a business
+ *
+ * @param sourceType sourceType of the contact. This is usually populated by {@link
+ * #mCachedNumberLookupService}.
+ */
+ public boolean isBusiness(int sourceType) {
+ return mCachedNumberLookupService != null && mCachedNumberLookupService.isBusiness(sourceType);
+ }
+
+ /**
+ * This function looks at a contact's source and determines if the user can mark caller ids from
+ * this source as invalid.
+ *
+ * @param sourceType The source type to be checked
+ * @param objectId The ID of the Contact object.
+ * @return true if contacts from this source can be marked with an invalid caller id
+ */
+ public boolean canReportAsInvalid(int sourceType, String objectId) {
+ return mCachedNumberLookupService != null
+ && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId);
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/PhoneLookupUtil.java b/java/com/android/dialer/phonenumbercache/PhoneLookupUtil.java
new file mode 100644
index 000000000..74175e8ba
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/PhoneLookupUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.PhoneLookup;
+
+public final class PhoneLookupUtil {
+
+ private PhoneLookupUtil() {}
+
+ /** @return the column name that stores contact id for phone lookup query. */
+ public static String getContactIdColumnNameForUri(Uri phoneLookupUri) {
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ return PhoneLookup.CONTACT_ID;
+ }
+ // In pre-N, contact id is stored in {@link PhoneLookup#_ID} in non-sip query.
+ boolean isSip =
+ phoneLookupUri.getBooleanQueryParameter(
+ ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, false);
+ return (isSip) ? PhoneLookup.CONTACT_ID : ContactsContract.PhoneLookup._ID;
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/PhoneNumberCache.java b/java/com/android/dialer/phonenumbercache/PhoneNumberCache.java
new file mode 100644
index 000000000..aefa544cb
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/PhoneNumberCache.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.content.Context;
+import java.util.Objects;
+
+/** Accessor for the phone number cache bindings. */
+public class PhoneNumberCache {
+
+ private static PhoneNumberCacheBindings phoneNumberCacheBindings;
+
+ private PhoneNumberCache() {}
+
+ public static PhoneNumberCacheBindings get(Context context) {
+ Objects.requireNonNull(context);
+ if (phoneNumberCacheBindings != null) {
+ return phoneNumberCacheBindings;
+ }
+
+ Context application = context.getApplicationContext();
+ if (application instanceof PhoneNumberCacheBindingsFactory) {
+ phoneNumberCacheBindings =
+ ((PhoneNumberCacheBindingsFactory) application).newPhoneNumberCacheBindings();
+ }
+
+ if (phoneNumberCacheBindings == null) {
+ phoneNumberCacheBindings = new PhoneNumberCacheBindingsStub();
+ }
+ return phoneNumberCacheBindings;
+ }
+
+ public static void setForTesting(PhoneNumberCacheBindings phoneNumberCacheBindings) {
+ PhoneNumberCache.phoneNumberCacheBindings = phoneNumberCacheBindings;
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindings.java b/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindings.java
new file mode 100644
index 000000000..6e3ed9d06
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindings.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.support.annotation.Nullable;
+
+/** Allows the container application provide a number look up service. */
+public interface PhoneNumberCacheBindings {
+
+ @Nullable
+ CachedNumberLookupService getCachedNumberLookupService();
+}
diff --git a/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsFactory.java b/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsFactory.java
new file mode 100644
index 000000000..3552529ba
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+/**
+ * This interface should be implementated by the Application subclass. It allows this module to get
+ * references to the PhoneNumberCacheBindings.
+ */
+public interface PhoneNumberCacheBindingsFactory {
+
+ PhoneNumberCacheBindings newPhoneNumberCacheBindings();
+}
diff --git a/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsStub.java b/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsStub.java
new file mode 100644
index 000000000..c7fb97807
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/PhoneNumberCacheBindingsStub.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.support.annotation.Nullable;
+
+/** Default implementation of PhoneNumberCacheBindings. */
+public class PhoneNumberCacheBindingsStub implements PhoneNumberCacheBindings {
+
+ @Override
+ @Nullable
+ public CachedNumberLookupService getCachedNumberLookupService() {
+ return null;
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/PhoneQuery.java b/java/com/android/dialer/phonenumbercache/PhoneQuery.java
new file mode 100644
index 000000000..5ddd5f846
--- /dev/null
+++ b/java/com/android/dialer/phonenumbercache/PhoneQuery.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.phonenumbercache;
+
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+
+/** The queries to look up the {@link ContactInfo} for a given number in the Call Log. */
+final class PhoneQuery {
+
+ public static final int PERSON_ID = 0;
+ public static final int NAME = 1;
+ public static final int PHONE_TYPE = 2;
+ public static final int LABEL = 3;
+ public static final int MATCHED_NUMBER = 4;
+ public static final int NORMALIZED_NUMBER = 5;
+ public static final int PHOTO_ID = 6;
+ public static final int LOOKUP_KEY = 7;
+ public static final int PHOTO_URI = 8;
+ /** Projection to look up a contact's DISPLAY_NAME_ALTERNATIVE */
+ public static final String[] DISPLAY_NAME_ALTERNATIVE_PROJECTION =
+ new String[] {
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ };
+
+ public static final int NAME_ALTERNATIVE = 0;
+
+ public static final String[] ADDITIONAL_CONTACT_INFO_PROJECTION =
+ new String[] {Phone.DISPLAY_NAME_ALTERNATIVE, Phone.CARRIER_PRESENCE};
+ public static final int ADDITIONAL_CONTACT_INFO_DISPLAY_NAME_ALTERNATIVE = 0;
+ public static final int ADDITIONAL_CONTACT_INFO_CARRIER_PRESENCE = 1;
+
+ /**
+ * Projection to look up the ContactInfo. Does not include DISPLAY_NAME_ALTERNATIVE as that column
+ * isn't available in ContactsCommon.PhoneLookup. We should always use this projection starting
+ * from NYC onward.
+ */
+ private static final String[] PHONE_LOOKUP_PROJECTION =
+ new String[] {
+ PhoneLookup.CONTACT_ID,
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.NUMBER,
+ PhoneLookup.NORMALIZED_NUMBER,
+ PhoneLookup.PHOTO_ID,
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.PHOTO_URI
+ };
+ /**
+ * Similar to {@link PHONE_LOOKUP_PROJECTION}. In pre-N, contact id is stored in {@link
+ * PhoneLookup#_ID} in non-sip query.
+ */
+ private static final String[] BACKWARD_COMPATIBLE_NON_SIP_PHONE_LOOKUP_PROJECTION =
+ new String[] {
+ PhoneLookup._ID,
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.NUMBER,
+ PhoneLookup.NORMALIZED_NUMBER,
+ PhoneLookup.PHOTO_ID,
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.PHOTO_URI
+ };
+
+ public static String[] getPhoneLookupProjection(Uri phoneLookupUri) {
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ return PHONE_LOOKUP_PROJECTION;
+ }
+ // Pre-N
+ boolean isSip =
+ phoneLookupUri.getBooleanQueryParameter(
+ ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, false);
+ return (isSip) ? PHONE_LOOKUP_PROJECTION : BACKWARD_COMPATIBLE_NON_SIP_PHONE_LOOKUP_PROJECTION;
+ }
+}