summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXiao-Long Chen <chenxiaolong@cxl.epac.to>2016-09-12 07:34:02 (GMT)
committerDan Pasanen <dan.pasanen@gmail.com>2018-02-08 03:12:56 (GMT)
commitfa4ae989a0738b9e6f05eefdab6cebd7c7c9c1bc (patch)
tree03df5283d70fc541457262dcf96822ed8d5d662e
parent56aa9e55be74732d0ec42229031f868c4d5906a8 (diff)
downloadandroid_packages_apps_Dialer-staging/lineage-15.1.zip
android_packages_apps_Dialer-staging/lineage-15.1.tar.gz
android_packages_apps_Dialer-staging/lineage-15.1.tar.bz2
Re-add dialer lookup.staging/lineage-15.1
BUGBASH-612: do not send phone numbers to non-ssl sites for reverse/forward/people lookups Change-Id: I677460ad5767b8698ee24d6d43ff159aee55387a
-rw-r--r--Android.mk3
-rw-r--r--java/com/android/dialer/app/calllog/ClearCallLogDialog.java2
-rw-r--r--java/com/android/dialer/app/list/RegularSearchFragment.java4
-rw-r--r--java/com/android/dialer/app/list/RegularSearchListAdapter.java14
-rw-r--r--java/com/android/dialer/app/settings/DialerSettingsActivity.java6
-rw-r--r--java/com/android/dialer/binary/aosp/AospDialerApplication.java49
-rw-r--r--java/com/android/dialer/lookup/AndroidManifest.xml31
-rw-r--r--java/com/android/dialer/lookup/ContactBuilder.java524
-rw-r--r--java/com/android/dialer/lookup/DirectoryId.java35
-rw-r--r--java/com/android/dialer/lookup/ForwardLookup.java62
-rw-r--r--java/com/android/dialer/lookup/LookupCache.java303
-rw-r--r--java/com/android/dialer/lookup/LookupProvider.java504
-rw-r--r--java/com/android/dialer/lookup/LookupSettings.java108
-rw-r--r--java/com/android/dialer/lookup/LookupSettingsFragment.java141
-rw-r--r--java/com/android/dialer/lookup/LookupUtils.java168
-rw-r--r--java/com/android/dialer/lookup/PeopleLookup.java55
-rw-r--r--java/com/android/dialer/lookup/ReverseLookup.java103
-rw-r--r--java/com/android/dialer/lookup/ReverseLookupService.java215
-rw-r--r--java/com/android/dialer/lookup/auskunft/AuskunftApi.java125
-rw-r--r--java/com/android/dialer/lookup/auskunft/AuskunftPeopleLookup.java46
-rw-r--r--java/com/android/dialer/lookup/auskunft/AuskunftReverseLookup.java47
-rw-r--r--java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java87
-rw-r--r--java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java69
-rw-r--r--java/com/android/dialer/lookup/google/GoogleForwardLookup.java259
-rw-r--r--java/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java109
-rw-r--r--java/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java171
-rw-r--r--java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_180_holo_light.pngbin0 -> 698 bytes
-rw-r--r--java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_holo_light.pngbin0 -> 424 bytes
-rw-r--r--java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_180_holo_light.pngbin0 -> 1140 bytes
-rw-r--r--java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_holo_light.pngbin0 -> 632 bytes
-rw-r--r--java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_180_holo_light.pngbin0 -> 1171 bytes
-rw-r--r--java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_holo_light.pngbin0 -> 636 bytes
-rw-r--r--java/com/android/dialer/lookup/res/values/cm_arrays.xml53
-rw-r--r--java/com/android/dialer/lookup/res/values/cm_strings.xml33
-rw-r--r--java/com/android/dialer/lookup/res/xml/lookup_settings.xml69
-rw-r--r--java/com/android/dialer/lookup/yellowpages/YellowPagesApi.java218
-rw-r--r--java/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java117
-rw-r--r--java/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java106
-rw-r--r--java/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java63
-rw-r--r--java/com/android/dialer/phonenumbercache/ContactInfoHelper.java3
40 files changed, 3900 insertions, 2 deletions
diff --git a/Android.mk b/Android.mk
index 4c5e663..3a5957e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -85,6 +85,7 @@ RES_DIRS := \
$(BASE_DIR)/dialer/dialpadview/res \
$(BASE_DIR)/dialer/enrichedcall/simulator/res \
$(BASE_DIR)/dialer/interactions/res \
+ $(BASE_DIR)/dialer/lookup/res \
$(BASE_DIR)/dialer/main/impl/res \
$(BASE_DIR)/dialer/notification/res \
$(BASE_DIR)/dialer/oem/res \
@@ -145,6 +146,7 @@ DIALER_MANIFEST_FILES += \
$(BASE_DIR)/dialer/dialpadview/AndroidManifest.xml \
$(BASE_DIR)/dialer/enrichedcall/simulator/AndroidManifest.xml \
$(BASE_DIR)/dialer/interactions/AndroidManifest.xml \
+ $(BASE_DIR)/dialer/lookup/AndroidManifest.xml \
$(BASE_DIR)/dialer/main/impl/AndroidManifest.xml \
$(BASE_DIR)/dialer/notification/AndroidManifest.xml \
$(BASE_DIR)/dialer/oem/AndroidManifest.xml \
@@ -300,6 +302,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
libphonenumber \
okhttp \
volley \
+ org.lineageos.platform.sdk
LOCAL_STATIC_ANDROID_LIBRARIES := \
android-support-design \
diff --git a/java/com/android/dialer/app/calllog/ClearCallLogDialog.java b/java/com/android/dialer/app/calllog/ClearCallLogDialog.java
index 5c3d4d9..68357e7 100644
--- a/java/com/android/dialer/app/calllog/ClearCallLogDialog.java
+++ b/java/com/android/dialer/app/calllog/ClearCallLogDialog.java
@@ -32,6 +32,7 @@ import android.provider.CallLog.Calls;
import android.support.annotation.NonNull;
import com.android.dialer.app.R;
import com.android.dialer.common.Assert;
+import com.android.dialer.lookup.LookupCache;
import com.android.dialer.phonenumbercache.CachedNumberLookupService;
import com.android.dialer.phonenumbercache.PhoneNumberCache;
@@ -70,6 +71,7 @@ public class ClearCallLogDialog extends DialogFragment {
if (cachedNumberLookupService != null) {
cachedNumberLookupService.clearAllCacheEntries(context);
}
+ LookupCache.deleteCachedContacts(context);
return null;
}
diff --git a/java/com/android/dialer/app/list/RegularSearchFragment.java b/java/com/android/dialer/app/list/RegularSearchFragment.java
index 728948b..133b88d 100644
--- a/java/com/android/dialer/app/list/RegularSearchFragment.java
+++ b/java/com/android/dialer/app/list/RegularSearchFragment.java
@@ -27,6 +27,7 @@ import com.android.contacts.common.list.PinnedHeaderListView;
import com.android.dialer.app.R;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.lookup.LookupCache;
import com.android.dialer.phonenumbercache.CachedNumberLookupService;
import com.android.dialer.phonenumbercache.PhoneNumberCache;
import com.android.dialer.util.PermissionsUtil;
@@ -71,11 +72,12 @@ public class RegularSearchFragment extends SearchFragment
protected void cacheContactInfo(int position) {
CachedNumberLookupService cachedNumberLookupService =
PhoneNumberCache.get(getContext()).getCachedNumberLookupService();
+ final RegularSearchListAdapter adapter = (RegularSearchListAdapter) getAdapter();
if (cachedNumberLookupService != null) {
- final RegularSearchListAdapter adapter = (RegularSearchListAdapter) getAdapter();
cachedNumberLookupService.addContact(
getContext(), adapter.getContactInfo(cachedNumberLookupService, position));
}
+ LookupCache.cacheContact(getContext(), adapter.getLookupContactInfo(position));
}
@Override
diff --git a/java/com/android/dialer/app/list/RegularSearchListAdapter.java b/java/com/android/dialer/app/list/RegularSearchListAdapter.java
index 94544d2..9bb38d3 100644
--- a/java/com/android/dialer/app/list/RegularSearchListAdapter.java
+++ b/java/com/android/dialer/app/list/RegularSearchListAdapter.java
@@ -39,6 +39,20 @@ public class RegularSearchListAdapter extends DialerPhoneNumberListAdapter {
setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, false);
}
+ public ContactInfo getLookupContactInfo(int position) {
+ ContactInfo info = new ContactInfo();
+ final Cursor item = (Cursor) getItem(position);
+ if (item != null) {
+ info.name = item.getString(PhoneQuery.DISPLAY_NAME);
+ info.type = item.getInt(PhoneQuery.PHONE_TYPE);
+ info.label = item.getString(PhoneQuery.PHONE_LABEL);
+ info.number = item.getString(PhoneQuery.PHONE_NUMBER);
+ final String photoUriStr = item.getString(PhoneQuery.PHOTO_URI);
+ info.photoUri = photoUriStr == null ? null : Uri.parse(photoUriStr);
+ }
+ return info;
+ }
+
public CachedContactInfo getContactInfo(CachedNumberLookupService lookupService, int position) {
ContactInfo info = new ContactInfo();
CachedContactInfo cacheInfo = lookupService.buildCachedContactInfo(info);
diff --git a/java/com/android/dialer/app/settings/DialerSettingsActivity.java b/java/com/android/dialer/app/settings/DialerSettingsActivity.java
index 6036c85..e0637a7 100644
--- a/java/com/android/dialer/app/settings/DialerSettingsActivity.java
+++ b/java/com/android/dialer/app/settings/DialerSettingsActivity.java
@@ -36,6 +36,7 @@ import com.android.dialer.about.AboutPhoneFragment;
import com.android.dialer.app.R;
import com.android.dialer.blocking.FilteredNumberCompat;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.lookup.LookupSettingsFragment;
import com.android.dialer.proguard.UsedByReflection;
import com.android.voicemail.VoicemailClient;
import com.android.voicemail.VoicemailComponent;
@@ -89,6 +90,11 @@ public class DialerSettingsActivity extends AppCompatPreferenceActivity {
quickResponseSettingsHeader.intent = quickResponseSettingsIntent;
target.add(quickResponseSettingsHeader);
+ final Header lookupSettingsHeader = new Header();
+ lookupSettingsHeader.titleRes = R.string.lookup_settings_label;
+ lookupSettingsHeader.fragment = LookupSettingsFragment.class.getName();
+ target.add(lookupSettingsHeader);
+
TelephonyManager telephonyManager =
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/java/com/android/dialer/binary/aosp/AospDialerApplication.java b/java/com/android/dialer/binary/aosp/AospDialerApplication.java
index 4ca94e2..9061f23 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerApplication.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerApplication.java
@@ -16,15 +16,30 @@
package com.android.dialer.binary.aosp;
+import android.content.Context;
+import android.net.Uri;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.contacts.common.extensions.PhoneDirectoryExtender;
+import com.android.contacts.common.extensions.PhoneDirectoryExtenderFactory;
+import com.android.contacts.common.list.DirectoryPartition;
import com.android.dialer.binary.common.DialerApplication;
import com.android.dialer.inject.ContextModule;
+import com.android.dialer.lookup.LookupProvider;
+import com.android.dialer.lookup.ReverseLookupService;
+import com.android.incallui.bindings.InCallUiBindings;
+import com.android.incallui.bindings.InCallUiBindingsFactory;
+import com.android.incallui.bindings.InCallUiBindingsStub;
+import com.android.incallui.bindings.PhoneNumberService;
+
+import java.util.List;
/**
* The application class for the AOSP Dialer. This is a version of the Dialer app that has no
* dependency on Google Play Services.
*/
-public class AospDialerApplication extends DialerApplication {
+public class AospDialerApplication extends DialerApplication
+ implements PhoneDirectoryExtenderFactory, InCallUiBindingsFactory {
/** Returns a new instance of the root component for the AOSP Dialer. */
@Override
@@ -32,4 +47,36 @@ public class AospDialerApplication extends DialerApplication {
protected Object buildRootComponent() {
return DaggerAospDialerRootComponent.builder().contextModule(new ContextModule(this)).build();
}
+
+ @Override
+ public PhoneDirectoryExtender newPhoneDirectoryExtender() {
+ return new PhoneDirectoryExtender() {
+ @Override
+ public List<DirectoryPartition> getExtendedDirectories(Context context) {
+ return LookupProvider.getExtendedDirectories(context);
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+ return false;
+ }
+
+ @Override
+ @Nullable
+ public Uri getContentUri() {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public InCallUiBindings newInCallUiBindings() {
+ return new InCallUiBindingsStub() {
+ @Override
+ @Nullable
+ public PhoneNumberService newPhoneNumberService(Context context) {
+ return new ReverseLookupService(context);
+ }
+ };
+ }
}
diff --git a/java/com/android/dialer/lookup/AndroidManifest.xml b/java/com/android/dialer/lookup/AndroidManifest.xml
new file mode 100644
index 0000000..0a278db
--- /dev/null
+++ b/java/com/android/dialer/lookup/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.dialer.lookup">
+
+ <uses-sdk android:minSdkVersion="23"/>
+ <uses-permission android:name="lineageos.permission.WRITE_SETTINGS"/>
+
+ <application>
+ <provider android:name="com.android.dialer.lookup.LookupProvider"
+ android:authorities="com.android.dialer.lookup"
+ android:exported="false"
+ android:multiprocess="false" />
+
+ </application>
+</manifest>
diff --git a/java/com/android/dialer/lookup/ContactBuilder.java b/java/com/android/dialer/lookup/ContactBuilder.java
new file mode 100644
index 0000000..12d4206
--- /dev/null
+++ b/java/com/android/dialer/lookup/ContactBuilder.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.contacts.common.util.Constants;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.R;
+
+import java.sql.Struct;
+import java.util.ArrayList;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.w3c.dom.Text;
+
+public class ContactBuilder {
+ private static final String TAG =
+ ContactBuilder.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ /** Used to choose the proper directory ID */
+ public static final int FORWARD_LOOKUP = 0;
+ public static final int PEOPLE_LOOKUP = 1;
+ public static final int REVERSE_LOOKUP = 2;
+
+ /** Default photo for businesses if no other image is found */
+ public static final String PHOTO_URI_BUSINESS =
+ new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority("com.android.dialer")
+ .appendPath(String.valueOf(
+ R.drawable.ic_places_picture_180_holo_light))
+ .build()
+ .toString();
+
+ private ArrayList<Address> mAddresses = new ArrayList<Address>();
+ private ArrayList<PhoneNumber> mPhoneNumbers
+ = new ArrayList<PhoneNumber>();
+ private ArrayList<WebsiteUrl> mWebsites
+ = new ArrayList<WebsiteUrl>();
+
+ private int mDirectoryType;
+ private long mDirectoryId = DirectoryId.DEFAULT;
+
+ private Name mName;
+
+ private String mNormalizedNumber;
+ private String mFormattedNumber;
+ private int mDisplayNameSource = DisplayNameSources.ORGANIZATION;
+ private Uri mPhotoUri;
+
+ private boolean mIsBusiness;
+
+ public ContactBuilder(int directoryType, String normalizedNumber,
+ String formattedNumber) {
+ mDirectoryType = directoryType;
+ mNormalizedNumber = normalizedNumber;
+ mFormattedNumber = formattedNumber;
+ }
+
+ public ContactBuilder(Uri encodedContactUri) throws JSONException {
+ String jsonData = encodedContactUri.getEncodedFragment();
+ String directoryId =
+ encodedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+ if (!TextUtils.isEmpty(directoryId)) {
+ try {
+ mDirectoryId = Long.parseLong(directoryId);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Error parsing directory id of uri " + encodedContactUri, e);
+ }
+ }
+ try {
+ // name
+ JSONObject json = new JSONObject(jsonData);
+ JSONObject contact = json.optJSONObject(Contacts.CONTENT_ITEM_TYPE);
+ JSONObject nameObj = contact.optJSONObject(StructuredName.CONTENT_ITEM_TYPE);
+ mName = new Name(nameObj);
+
+ if (contact != null) {
+ // numbers
+ if (contact.has(Phone.CONTENT_ITEM_TYPE)) {
+ String phoneData = contact.getString(Phone.CONTENT_ITEM_TYPE);
+ Object phoneObject = new JSONTokener(phoneData).nextValue();
+ JSONArray phoneNumbers;
+ if (phoneObject instanceof JSONObject) {
+ phoneNumbers = new JSONArray();
+ phoneNumbers.put(phoneObject);
+ } else {
+ phoneNumbers = contact.getJSONArray(Phone.CONTENT_ITEM_TYPE);
+ }
+ for (int i = 0; i < phoneNumbers.length(); ++i) {
+ JSONObject phoneObj = phoneNumbers.getJSONObject(i);
+ mPhoneNumbers.add(new PhoneNumber(phoneObj));
+ }
+ }
+
+ // address
+ if (contact.has(StructuredPostal.CONTENT_ITEM_TYPE)) {
+ JSONArray addresses = contact.getJSONArray(StructuredPostal.CONTENT_ITEM_TYPE);
+ for (int i = 0; i < addresses.length(); ++i) {
+ JSONObject addrObj = addresses.getJSONObject(i);
+ mAddresses.add(new Address(addrObj));
+ }
+ }
+
+ // websites
+ if (contact.has(Website.CONTENT_ITEM_TYPE)) {
+ JSONArray websites = contact.getJSONArray(Website.CONTENT_ITEM_TYPE);
+ for (int i = 0; i < websites.length(); ++i) {
+ JSONObject websiteObj = websites.getJSONObject(i);
+ final WebsiteUrl websiteUrl = new WebsiteUrl(websiteObj);
+ if (!TextUtils.isEmpty(websiteUrl.url)) {
+ mWebsites.add(new WebsiteUrl(websiteObj));
+ }
+ }
+ }
+ }
+
+ }
+ catch(JSONException e) {
+ Log.e(TAG, "Error parsing encoded fragment of uri " + encodedContactUri, e);
+ throw e;
+ }
+ }
+
+ public void addAddress(Address address) {
+ if (DEBUG) Log.d(TAG, "Adding address");
+ if (address != null) {
+ mAddresses.add(address);
+ }
+ }
+
+ public Address[] getAddresses() {
+ return mAddresses.toArray(new Address[mAddresses.size()]);
+ }
+
+ public void addPhoneNumber(PhoneNumber phoneNumber) {
+ if (DEBUG) Log.d(TAG, "Adding phone number");
+ if (phoneNumber != null) {
+ mPhoneNumbers.add(phoneNumber);
+ }
+ }
+
+ public PhoneNumber[] getPhoneNumbers() {
+ return mPhoneNumbers.toArray(
+ new PhoneNumber[mPhoneNumbers.size()]);
+ }
+
+ public void addWebsite(WebsiteUrl website) {
+ if (DEBUG) Log.d(TAG, "Adding website");
+ if (website != null) {
+ mWebsites.add(website);
+ }
+ }
+
+ public WebsiteUrl[] getWebsites() {
+ return mWebsites.toArray(new WebsiteUrl[mWebsites.size()]);
+ }
+
+ public void setName(Name name) {
+ if (DEBUG) Log.d(TAG, "Setting name");
+ if (name != null) {
+ mName = name;
+ }
+ }
+
+ public Name getName() {
+ return mName;
+ }
+
+ public void setPhotoUri(String photoUri) {
+ if (photoUri != null) {
+ setPhotoUri(Uri.parse(photoUri));
+ }
+ }
+
+ public void setPhotoUri(Uri photoUri) {
+ if (DEBUG) Log.d(TAG, "Setting photo URI");
+ mPhotoUri = photoUri;
+ }
+
+ public Uri getPhotoUri() {
+ return mPhotoUri;
+ }
+
+ public long getDirectoryId() {
+ return mDirectoryId;
+ }
+
+ public void setIsBusiness(boolean isBusiness) {
+ if (DEBUG) Log.d(TAG, "Setting isBusiness to " + isBusiness);
+ mIsBusiness = isBusiness;
+ }
+
+ public boolean isBusiness() {
+ return mIsBusiness;
+ }
+
+ public ContactInfo build() {
+ if (mName == null) {
+ throw new IllegalStateException("Name has not been set");
+ }
+
+ if (mDirectoryType != FORWARD_LOOKUP
+ && mDirectoryType != PEOPLE_LOOKUP
+ && mDirectoryType != REVERSE_LOOKUP) {
+ throw new IllegalStateException("Invalid directory type");
+ }
+
+ // Use the incoming call's phone number if no other phone number
+ // is specified. The reverse lookup source could present the phone
+ // number differently (eg. without the area code).
+ if (mPhoneNumbers.size() == 0) {
+ PhoneNumber pn = new PhoneNumber();
+ // Use the formatted number where possible
+ pn.number = mFormattedNumber != null
+ ? mFormattedNumber : mNormalizedNumber;
+ pn.type = Phone.TYPE_MAIN;
+ addPhoneNumber(pn);
+ }
+
+ try {
+ JSONObject contact = new JSONObject();
+
+ // Insert the name
+ contact.put(StructuredName.CONTENT_ITEM_TYPE,
+ mName.getJsonObject());
+
+ // Insert phone numbers
+ JSONArray phoneNumbers = new JSONArray();
+ for (int i = 0; i < mPhoneNumbers.size(); i++) {
+ phoneNumbers.put(mPhoneNumbers.get(i).getJsonObject());
+ }
+ contact.put(Phone.CONTENT_ITEM_TYPE, phoneNumbers);
+
+ // Insert addresses if there are any
+ if (mAddresses.size() > 0) {
+ JSONArray addresses = new JSONArray();
+ for (int i = 0; i < mAddresses.size(); i++) {
+ addresses.put(mAddresses.get(i).getJsonObject());
+ }
+ contact.put(StructuredPostal.CONTENT_ITEM_TYPE, addresses);
+ }
+
+ // Insert websites if there are any
+ if (mWebsites.size() > 0) {
+ JSONArray websites = new JSONArray();
+ for (int i = 0; i < mWebsites.size(); i++) {
+ websites.put(mWebsites.get(i).getJsonObject());
+ }
+ contact.put(Website.CONTENT_ITEM_TYPE, websites);
+ }
+
+ ContactInfo info = new ContactInfo();
+ info.name = mName.displayName;
+ info.normalizedNumber = mNormalizedNumber;
+ info.number = mPhoneNumbers.get(0).number;
+ info.type = mPhoneNumbers.get(0).type;
+ info.label = mPhoneNumbers.get(0).label;
+ info.photoUri = mPhotoUri != null ? mPhotoUri : null;
+
+ String json = new JSONObject()
+ .put(Contacts.DISPLAY_NAME, mName.displayName)
+ .put(Contacts.DISPLAY_NAME_SOURCE, mDisplayNameSource)
+ .put(Directory.EXPORT_SUPPORT,
+ Directory.EXPORT_SUPPORT_ANY_ACCOUNT)
+ .put(Contacts.CONTENT_ITEM_TYPE, contact)
+ .toString();
+
+ if (json != null) {
+ long directoryId = -1;
+ switch (mDirectoryType) {
+ case FORWARD_LOOKUP:
+ directoryId = DirectoryId.NEARBY;
+ break;
+ case PEOPLE_LOOKUP:
+ directoryId = DirectoryId.PEOPLE;
+ break;
+ case REVERSE_LOOKUP:
+ // use null directory to be backwards compatible with old code
+ directoryId = DirectoryId.NULL;
+ break;
+ }
+
+ info.lookupUri = Contacts.CONTENT_LOOKUP_URI
+ .buildUpon()
+ .appendPath(Constants.LOOKUP_URI_ENCODED)
+ .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(directoryId))
+ .encodedFragment(json)
+ .build();
+ }
+
+ return info;
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to build contact", e);
+ return null;
+ }
+ }
+
+ // android.provider.ContactsContract.CommonDataKinds.StructuredPostal
+ public static class Address {
+ public String formattedAddress;
+ public int type;
+ public String label;
+ public String street;
+ public String poBox;
+ public String neighborhood;
+ public String city;
+ public String region;
+ public String postCode;
+ public String country;
+
+ public static Address createFormattedHome(String address) {
+ Address a = new Address();
+ a.formattedAddress = address;
+ a.type = StructuredPostal.TYPE_HOME;
+ return a;
+ }
+
+ public JSONObject getJsonObject() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.putOpt(StructuredPostal.FORMATTED_ADDRESS,
+ formattedAddress);
+ json.put(StructuredPostal.TYPE, type);
+ json.putOpt(StructuredPostal.LABEL, label);
+ json.putOpt(StructuredPostal.STREET, street);
+ json.putOpt(StructuredPostal.POBOX, poBox);
+ json.putOpt(StructuredPostal.NEIGHBORHOOD, neighborhood);
+ json.putOpt(StructuredPostal.CITY, city);
+ json.putOpt(StructuredPostal.REGION, region);
+ json.putOpt(StructuredPostal.POSTCODE, postCode);
+ json.putOpt(StructuredPostal.COUNTRY, country);
+ return json;
+ }
+
+ public Address() {}
+
+ public Address(JSONObject json) throws JSONException {
+ if (json.has(StructuredPostal.FORMATTED_ADDRESS)) {
+ formattedAddress = json.getString(StructuredPostal.FORMATTED_ADDRESS);
+ }
+ }
+
+ public String toString() {
+ return "formattedAddress: " + formattedAddress + "; " +
+ "type: " + type + "; " +
+ "label: " + label + "; " +
+ "street: " + street + "; " +
+ "poBox: " + poBox + "; " +
+ "neighborhood: " + neighborhood + "; " +
+ "city: " + city + "; " +
+ "region: " + region + "; " +
+ "postCode: " + postCode + "; " +
+ "country: " + country;
+ }
+ }
+
+ // android.provider.ContactsContract.CommonDataKinds.StructuredName
+ public static class Name {
+ public String displayName;
+ public String givenName;
+ public String familyName;
+ public String prefix;
+ public String middleName;
+ public String suffix;
+ public String phoneticGivenName;
+ public String phoneticMiddleName;
+ public String phoneticFamilyName;
+
+ public static Name createDisplayName(String displayName) {
+ Name name = new Name();
+ name.displayName = displayName;
+ return name;
+ }
+
+ public JSONObject getJsonObject() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.putOpt(StructuredName.DISPLAY_NAME, displayName);
+ json.putOpt(StructuredName.GIVEN_NAME, givenName);
+ json.putOpt(StructuredName.FAMILY_NAME, familyName);
+ json.putOpt(StructuredName.PREFIX, prefix);
+ json.putOpt(StructuredName.MIDDLE_NAME, middleName);
+ json.putOpt(StructuredName.SUFFIX, suffix);
+ json.putOpt(StructuredName.PHONETIC_GIVEN_NAME,
+ phoneticGivenName);
+ json.putOpt(StructuredName.PHONETIC_MIDDLE_NAME,
+ phoneticMiddleName);
+ json.putOpt(StructuredName.PHONETIC_FAMILY_NAME,
+ phoneticFamilyName);
+ return json;
+ }
+
+ public Name(JSONObject json) throws JSONException {
+ if (json != null) {
+ displayName = json.optString(StructuredName.DISPLAY_NAME, null);
+ }
+ }
+
+ public Name() {}
+
+ public String toString() {
+ return "displayName: " + displayName + "; " +
+ "givenName: " + givenName + "; " +
+ "familyName: " + familyName + "; " +
+ "prefix: " + prefix + "; " +
+ "middleName: " + middleName + "; " +
+ "suffix: " + suffix + "; " +
+ "phoneticGivenName: " + phoneticGivenName + "; " +
+ "phoneticMiddleName: " + phoneticMiddleName + "; " +
+ "phoneticFamilyName: " + phoneticFamilyName;
+ }
+ }
+
+ // android.provider.ContactsContract.CommonDataKinds.Phone
+ public static class PhoneNumber {
+ public String number;
+ public int type;
+ public String label;
+
+ public static PhoneNumber createMainNumber(String number) {
+ PhoneNumber n = new PhoneNumber();
+ n.number = number;
+ n.type = Phone.TYPE_MAIN;
+ return n;
+ }
+
+ public JSONObject getJsonObject() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put(Phone.NUMBER, number);
+ json.put(Phone.TYPE, type);
+ json.putOpt(Phone.LABEL, label);
+ return json;
+ }
+
+ public PhoneNumber(JSONObject json) throws JSONException {
+ number = json.getString(Phone.NUMBER);
+ type = json.getInt(Phone.TYPE);
+ if (json.has(Phone.LABEL)) {
+ label = json.getString(Phone.LABEL);
+ }
+ }
+
+ public PhoneNumber() {}
+
+ public String toString() {
+ return "number: " + number + "; " +
+ "type: " + type + "; " +
+ "label: " + label;
+ }
+ }
+
+ // android.provider.ContactsContract.CommonDataKinds.Website
+ public static class WebsiteUrl {
+ public String url;
+ public int type;
+ public String label;
+
+ public static WebsiteUrl createProfile(String url) {
+ WebsiteUrl u = new WebsiteUrl();
+ u.url = url;
+ u.type = Website.TYPE_PROFILE;
+ return u;
+ }
+
+ public JSONObject getJsonObject() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put(Website.URL, url);
+ json.put(Website.TYPE, type);
+ json.putOpt(Website.LABEL, label);
+ return json;
+ }
+
+ public WebsiteUrl() {}
+
+ public WebsiteUrl(JSONObject json) throws JSONException {
+ if (json.has(Website.URL)) {
+ url = json.getString(Website.URL);
+ }
+ if (json.has(Website.TYPE)) {
+ type = json.getInt(Website.TYPE);
+ }
+ if (json.has(Website.LABEL)) {
+ label = json.getString(Website.LABEL);
+ }
+ }
+
+ public String toString() {
+ return "url: " + url + "; " +
+ "type: " + type + "; " +
+ "label: " + label;
+ }
+ }
+}
diff --git a/java/com/android/dialer/lookup/DirectoryId.java b/java/com/android/dialer/lookup/DirectoryId.java
new file mode 100644
index 0000000..9749dc4
--- /dev/null
+++ b/java/com/android/dialer/lookup/DirectoryId.java
@@ -0,0 +1,35 @@
+package com.android.dialer.lookup;
+
+import android.net.Uri;
+import android.provider.ContactsContract;
+
+public class DirectoryId {
+
+ // default contacts directory
+ public static final long DEFAULT = ContactsContract.Directory.DEFAULT;
+
+ // id for a non existant directory
+ public static final long NULL = Long.MAX_VALUE;
+
+ // id for nearby forward lookup results (not a real directory)
+ public static final long NEARBY = NULL - 1;
+
+ // id for people forward lookup results (not a real directory)
+ public static final long PEOPLE = NULL - 2;
+
+ public static boolean isFakeDirectory(long directory) {
+ return directory == NULL || directory == NEARBY || directory == PEOPLE;
+ }
+
+ public static long fromUri(Uri lookupUri) {
+ long directory = DirectoryId.DEFAULT;
+ if (lookupUri != null) {
+ String dqp =
+ lookupUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+ if (dqp != null) {
+ directory = Long.valueOf(dqp);
+ }
+ }
+ return directory;
+ }
+}
diff --git a/java/com/android/dialer/lookup/ForwardLookup.java b/java/com/android/dialer/lookup/ForwardLookup.java
new file mode 100644
index 0000000..f65b320
--- /dev/null
+++ b/java/com/android/dialer/lookup/ForwardLookup.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.google.GoogleForwardLookup;
+import com.android.dialer.lookup.openstreetmap.OpenStreetMapForwardLookup;
+
+import android.content.Context;
+import android.location.Location;
+import android.util.Log;
+
+public abstract class ForwardLookup {
+ private static final String TAG = ForwardLookup.class.getSimpleName();
+
+ private static ForwardLookup INSTANCE = null;
+
+ public static ForwardLookup getInstance(Context context) {
+ String provider = LookupSettings.getForwardLookupProvider(context);
+
+ if (INSTANCE == null || !isInstance(provider)) {
+ Log.d(TAG, "Chosen forward lookup provider: " + provider);
+
+ if (provider.equals(LookupSettings.FLP_GOOGLE)) {
+ INSTANCE = new GoogleForwardLookup(context);
+ } else if (provider.equals(LookupSettings.FLP_OPENSTREETMAP)) {
+ INSTANCE = new OpenStreetMapForwardLookup(context);
+ }
+ }
+
+ return INSTANCE;
+ }
+
+ private static boolean isInstance(String provider) {
+ if (provider.equals(LookupSettings.FLP_GOOGLE)
+ && INSTANCE instanceof GoogleForwardLookup) {
+ return true;
+ } else if (provider.equals(LookupSettings.FLP_OPENSTREETMAP)
+ && INSTANCE instanceof OpenStreetMapForwardLookup) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public abstract ContactInfo[] lookup(Context context,
+ String filter, Location lastLocation);
+}
diff --git a/java/com/android/dialer/lookup/LookupCache.java b/java/com/android/dialer/lookup/LookupCache.java
new file mode 100644
index 0000000..e6d5cc6
--- /dev/null
+++ b/java/com/android/dialer/lookup/LookupCache.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chenxiaolong@cxl.epac.to>
+ *
+ * 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.lookup;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.dialer.util.DialerUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class LookupCache {
+ private static final String TAG = LookupCache.class.getSimpleName();
+
+ public static final String NAME = "Name";
+ public static final String TYPE = "Type";
+ public static final String LABEL = "Label";
+ public static final String NUMBER = "Number";
+ public static final String FORMATTED_NUMBER = "FormattedNumber";
+ public static final String NORMALIZED_NUMBER = "NormalizedNumber";
+ public static final String PHOTO_ID = "PhotoID";
+ //public static final String PHOTO_URI = "PhotoURI";
+ public static final String LOOKUP_URI = "LookupURI";
+
+ public static boolean hasCachedContact(Context context, String number) {
+ String normalizedNumber = formatE164(context, number);
+
+ if (normalizedNumber == null) {
+ return false;
+ }
+
+ File file = getFilePath(context, normalizedNumber);
+ return file.exists();
+ }
+
+ public static void cacheContact(Context context, ContactInfo info) {
+ File file = getFilePath(context, info.normalizedNumber);
+
+ if (file.exists()) {
+ file.delete();
+ }
+
+ FileOutputStream out = null;
+ JsonWriter writer = null;
+
+ try {
+ out = new FileOutputStream(file);
+ writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ writer.setIndent(" ");
+ List messages = new ArrayList();
+
+ writer.beginObject();
+ if (info.name != null) writer.name(NAME).value(info.name);
+ writer.name(TYPE).value(info.type);
+ if (info.label != null) writer.name(LABEL).value(info.label);
+ if (info.number != null) writer.name(NUMBER).value(info.number);
+ if (info.formattedNumber != null) {
+ writer.name(FORMATTED_NUMBER).value(info.formattedNumber);
+ }
+ if (info.normalizedNumber != null) {
+ writer.name(NORMALIZED_NUMBER).value(info.normalizedNumber);
+ }
+ writer.name(PHOTO_ID).value(info.photoId);
+
+ if (info.lookupUri != null) {
+ writer.name(LOOKUP_URI).value(info.lookupUri.toString());
+ }
+
+ // We do not save the photo URI. If there's a cached image, that
+ // will be used when the contact is retrieved. Otherwise, photoUri
+ // will be set to null.
+
+ writer.endObject();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ DialerUtils.closeQuietly(writer);
+ DialerUtils.closeQuietly(out);
+ }
+ }
+
+ public static ContactInfo getCachedContact(Context context, String number) {
+ String normalizedNumber = formatE164(context, number);
+
+ if (normalizedNumber == null) {
+ return null;
+ }
+
+ File file = getFilePath(context, normalizedNumber);
+ if (!file.exists()) {
+ // Whatever is calling this should probably check anyway
+ return null;
+ }
+
+ ContactInfo info = new ContactInfo();
+
+ FileInputStream in = null;
+ JsonReader reader = null;
+
+ try {
+ in = new FileInputStream(file);
+ reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+
+ if (NAME.equals(name)) {
+ info.name = reader.nextString();
+ } else if (TYPE.equals(name)) {
+ info.type = reader.nextInt();
+ } else if (LABEL.equals(name)) {
+ info.label = reader.nextString();
+ } else if (NUMBER.equals(name)) {
+ info.number = reader.nextString();
+ } else if (FORMATTED_NUMBER.equals(name)) {
+ info.formattedNumber = reader.nextString();
+ } else if (NORMALIZED_NUMBER.equals(name)) {
+ info.normalizedNumber = reader.nextString();
+ } else if (PHOTO_ID.equals(name)) {
+ info.photoId = reader.nextInt();
+ } else if (LOOKUP_URI.equals(name)) {
+ Uri lookupUri = Uri.parse(reader.nextString());
+
+ if (hasCachedImage(context, normalizedNumber)) {
+ // Insert cached photo URI
+ Uri image = Uri.withAppendedPath(
+ LookupProvider.IMAGE_CACHE_URI,
+ Uri.encode(normalizedNumber));
+
+ String json = lookupUri.getEncodedFragment();
+ if (json != null) {
+ try {
+ JSONObject jsonObj = new JSONObject(json);
+ jsonObj.putOpt(Contacts.PHOTO_URI, image.toString());
+ lookupUri = lookupUri.buildUpon()
+ .encodedFragment(jsonObj.toString())
+ .build();
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to add image URI to json", e);
+ }
+ }
+
+ info.photoUri = image;
+ }
+
+ info.lookupUri = lookupUri;
+ }
+ }
+ reader.endObject();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ DialerUtils.closeQuietly(reader);
+ DialerUtils.closeQuietly(in);
+ }
+
+ return info;
+ }
+
+ public static void deleteCachedContacts(Context context) {
+ File dir = new File(context.getCacheDir()
+ + File.separator + "lookup");
+
+ if (!dir.exists()) {
+ Log.v(TAG, "Lookup cache directory does not exist. Not clearing it.");
+ return;
+ }
+
+ if (!dir.isDirectory()) {
+ Log.e(TAG, "Path " + dir + " is not a directory");
+ return;
+ }
+
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile()) {
+ file.delete();
+ }
+ }
+ }
+ }
+
+ public static void deleteCachedContact(
+ Context context, String normalizedNumber) {
+ File f = getFilePath(context, normalizedNumber);
+ if (f.exists()) {
+ f.delete();
+ }
+
+ f = getImagePath(context, normalizedNumber);
+ if (f.exists()) {
+ f.delete();
+ }
+ }
+
+ public static boolean hasCachedImage(Context context, String number) {
+ String normalizedNumber = formatE164(context, number);
+
+ if (normalizedNumber == null) {
+ return false;
+ }
+
+ File file = getImagePath(context, normalizedNumber);
+ return file.exists();
+ }
+
+ public static void cacheImage(Context context,
+ String normalizedNumber, Bitmap bmp) {
+ // Compress the cached images to save space
+ if (bmp == null) {
+ Log.e(TAG, "Failed to cache image");
+ return;
+ }
+
+ File image = getImagePath(context, normalizedNumber);
+
+ FileOutputStream out = null;
+
+ try {
+ out = new FileOutputStream(image);
+ bmp.compress(Bitmap.CompressFormat.WEBP, 100, out);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ DialerUtils.closeQuietly(out);
+ }
+ }
+
+ public static Bitmap getCachedImage(Context context, String normalizedNumber) {
+ File image = getImagePath(context, normalizedNumber);
+ if (!image.exists()) {
+ return null;
+ }
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeFile(image.getPath(), options);
+ }
+
+ private static String formatE164(Context context, String number) {
+ String countryIso = ((TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE)).getSimCountryIso().toUpperCase();
+ return PhoneNumberUtils.formatNumberToE164(number, countryIso);
+ }
+
+ private static File getFilePath(Context context, String normalizedNumber) {
+ File dir = new File(context.getCacheDir()
+ + File.separator + "lookup");
+
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ return new File(dir, normalizedNumber + ".json");
+ }
+
+ public static File getImagePath(Context context, String normalizedNumber) {
+ File dir = new File(context.getCacheDir()
+ + File.separator + "lookup");
+
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ return new File(dir, normalizedNumber + ".webp");
+ }
+}
diff --git a/java/com/android/dialer/lookup/LookupProvider.java b/java/com/android/dialer/lookup/LookupProvider.java
new file mode 100644
index 0000000..b62a94a
--- /dev/null
+++ b/java/com/android/dialer/lookup/LookupProvider.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.contacts.common.list.DirectoryPartition;
+import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.R;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class LookupProvider extends ContentProvider {
+ private static final String TAG = LookupProvider.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ public static List<DirectoryPartition> getExtendedDirectories(Context context) {
+ ArrayList<DirectoryPartition> list = new ArrayList<DirectoryPartition>();
+
+ // The directories are shown in reverse order, so insert forward lookup
+ // last to make it show up at the top
+
+ if (LookupSettings.isPeopleLookupEnabled(context)) {
+ DirectoryPartition dp = new DirectoryPartition(false, true);
+ dp.setContentUri(PEOPLE_LOOKUP_URI.toString());
+ dp.setLabel(context.getString(R.string.people));
+ dp.setPriorityDirectory(false);
+ dp.setPhotoSupported(true);
+ dp.setDisplayNumber(false);
+ dp.setResultLimit(3);
+ list.add(dp);
+ } else {
+ Log.i(TAG, "Forward lookup (people) is disabled");
+ }
+
+ if (LookupSettings.isForwardLookupEnabled(context)) {
+ DirectoryPartition dp = new DirectoryPartition(false, true);
+ dp.setContentUri(NEARBY_LOOKUP_URI.toString());
+ dp.setLabel(context.getString(R.string.nearby_places));
+ dp.setPriorityDirectory(false);
+ dp.setPhotoSupported(true);
+ dp.setDisplayNumber(false);
+ dp.setResultLimit(3);
+ list.add(dp);
+ } else {
+ Log.i(TAG, "Forward lookup (nearby places) is disabled");
+ }
+
+ return list;
+ }
+
+ public static final String AUTHORITY = "com.android.dialer.lookup";
+ public static final Uri AUTHORITY_URI =
+ Uri.parse("content://" + AUTHORITY);
+ public static final Uri NEARBY_LOOKUP_URI =
+ Uri.withAppendedPath(AUTHORITY_URI, "nearby");
+ public static final Uri PEOPLE_LOOKUP_URI =
+ Uri.withAppendedPath(AUTHORITY_URI, "people");
+ public static final Uri IMAGE_CACHE_URI =
+ Uri.withAppendedPath(AUTHORITY_URI, "images");
+
+ private static final UriMatcher sURIMatcher = new UriMatcher(-1);
+ private final LinkedList<FutureTask> mActiveTasks =
+ new LinkedList<FutureTask>();
+
+ private static final int NEARBY = 0;
+ private static final int PEOPLE = 1;
+ private static final int IMAGE = 2;
+
+ static {
+ sURIMatcher.addURI(AUTHORITY, "nearby/*", NEARBY);
+ sURIMatcher.addURI(AUTHORITY, "people/*", PEOPLE);
+ sURIMatcher.addURI(AUTHORITY, "images/*", IMAGE);
+ }
+
+ private class FutureCallable<T> implements Callable<T> {
+ private final Callable<T> mCallable;
+ private volatile FutureTask<T> mFuture;
+
+ public FutureCallable(Callable<T> callable) {
+ mFuture = null;
+ mCallable = callable;
+ }
+
+ public T call() throws Exception {
+ Log.v(TAG, "Future called for " + Thread.currentThread().getName());
+
+ T result = mCallable.call();
+ if (mFuture == null) {
+ return result;
+ }
+
+ synchronized (mActiveTasks) {
+ mActiveTasks.remove(mFuture);
+ }
+
+ mFuture = null;
+ return result;
+ }
+
+ public void setFuture(FutureTask<T> future) {
+ mFuture = future;
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, final String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (DEBUG) Log.v(TAG, "query: " + uri);
+
+ Location lastLocation = null;
+ final int match = sURIMatcher.match(uri);
+
+ switch (match) {
+ case NEARBY:
+ if (!PermissionsUtil.hasLocationPermissions(getContext())) {
+ Log.v(TAG, "Location permission is missing, ignoring query.");
+ return null;
+ }
+ if (!isLocationEnabled()) {
+ Log.v(TAG, "Location settings is disabled, ignoring query.");
+ return null;
+ }
+ lastLocation = getLastLocation();
+ if (lastLocation == null) {
+ Log.v(TAG, "No location available, ignoring query.");
+ return null;
+ }
+ // fall through to the actual query
+
+ case PEOPLE:
+ final String filter = Uri.encode(uri.getLastPathSegment());
+ String limit = uri.getQueryParameter(ContactsContract.LIMIT_PARAM_KEY);
+
+ int maxResults = -1;
+
+ try {
+ if (limit != null) {
+ maxResults = Integer.parseInt(limit);
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "query: invalid limit parameter: '" + limit + "'");
+ }
+
+ final Location finalLastLocation = lastLocation;
+ final int finalMaxResults = maxResults;
+
+ return execute(new Callable<Cursor>() {
+ @Override
+ public Cursor call() {
+ return handleFilter(match, projection, filter,
+ finalMaxResults, finalLastLocation);
+ }
+ }, "FilterThread");
+ }
+
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("insert() not supported");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException("update() not supported");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("delete() not supported");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ int match = sURIMatcher.match(uri);
+
+ switch (match) {
+ case NEARBY:
+ case PEOPLE:
+ return Contacts.CONTENT_ITEM_TYPE;
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ switch (sURIMatcher.match(uri)) {
+ case IMAGE:
+ String number = uri.getLastPathSegment();
+
+ File image = LookupCache.getImagePath(getContext(), number);
+
+ if (mode.equals("r")) {
+ if (image == null || !image.exists() || !image.isFile()) {
+ throw new FileNotFoundException("Cached image does not exist");
+ }
+
+ return ParcelFileDescriptor.open(image,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ } else {
+ throw new FileNotFoundException("The URI is read only");
+ }
+
+ default:
+ throw new FileNotFoundException("Invalid URI: " + uri);
+ }
+ }
+
+ /**
+ * Check if the location services is on.
+ *
+ * @return Whether location services are enabled
+ */
+ private boolean isLocationEnabled() {
+ try {
+ int mode = Settings.Secure.getInt(
+ getContext().getContentResolver(),
+ Settings.Secure.LOCATION_MODE);
+
+ return mode != Settings.Secure.LOCATION_MODE_OFF;
+ } catch (Settings.SettingNotFoundException e) {
+ Log.e(TAG, "Failed to get location mode", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get location from last location query.
+ *
+ * @return The last location
+ */
+ private Location getLastLocation() {
+ LocationManager locationManager = (LocationManager)
+ getContext().getSystemService(Context.LOCATION_SERVICE);
+
+ try {
+ locationManager.requestSingleUpdate(new Criteria(),
+ new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+ }, Looper.getMainLooper());
+
+ return locationManager.getLastLocation();
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Process filter/query and perform the lookup.
+ *
+ * @param projection Columns to include in query
+ * @param filter String to lookup
+ * @param maxResults Maximum number of results
+ * @param lastLocation Coordinates of last location query
+ * @return Cursor for the results
+ */
+ private Cursor handleFilter(int type, String[] projection, String filter,
+ int maxResults, Location lastLocation) {
+ if (DEBUG) Log.v(TAG, "handleFilter(" + filter + ")");
+
+ if (filter != null) {
+ try {
+ filter = URLDecoder.decode(filter, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ }
+
+ ContactInfo[] results = null;
+ if (type == NEARBY) {
+ ForwardLookup fl = ForwardLookup.getInstance(getContext());
+ results = fl.lookup(getContext(), filter, lastLocation);
+ } else if (type == PEOPLE) {
+ PeopleLookup pl = PeopleLookup.getInstance(getContext());
+ results = pl.lookup(getContext(), filter);
+ }
+
+ if (results == null || results.length == 0) {
+ if (DEBUG) Log.v(TAG, "handleFilter(" + filter + "): No results");
+ return null;
+ }
+
+ Cursor cur = null;
+ try {
+ cur = buildResultCursor(projection, results, maxResults);
+
+ if (DEBUG) Log.v(TAG, "handleFilter(" + filter + "): "
+ + cur.getCount() + " matches");
+ } catch (JSONException e) {
+ Log.e(TAG, "JSON failure", e);
+ }
+
+ return cur;
+ }
+
+ return null;
+ }
+
+ /**
+ * Query results.
+ *
+ * @param projection Columns to include in query
+ * @param results Results for the forward lookup
+ * @param maxResults Maximum number of rows/results to add to cursor
+ * @return Cursor for forward lookup query results
+ */
+ private Cursor buildResultCursor(String[] projection,
+ ContactInfo[] results, int maxResults)
+ throws JSONException {
+ // Extended directories always use this projection
+ MatrixCursor cursor = new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
+
+ int id = 1;
+
+ for (int i = 0; i < results.length; i++) {
+ Object[] row = new Object[PhoneQuery.PROJECTION_PRIMARY.length];
+
+ row[PhoneQuery.PHONE_ID] = id;
+ row[PhoneQuery.PHONE_TYPE] = results[i].type;
+ row[PhoneQuery.PHONE_LABEL] = getAddress(results[i]);
+ row[PhoneQuery.PHONE_NUMBER] = results[i].number;
+ row[PhoneQuery.CONTACT_ID] = id;
+ row[PhoneQuery.LOOKUP_KEY] = results[i].lookupUri.getEncodedFragment();
+ row[PhoneQuery.PHOTO_ID] = 0;
+ row[PhoneQuery.DISPLAY_NAME] = results[i].name;
+ row[PhoneQuery.PHOTO_URI] = results[i].photoUri;
+
+ cursor.addRow(row);
+
+ if (maxResults != -1 && cursor.getCount() >= maxResults) {
+ break;
+ }
+
+ id++;
+ }
+
+ return cursor;
+ }
+
+ private String getAddress(ContactInfo info) {
+ // Hack: Show city or address for phone label, so they appear in
+ // the results list
+
+ String city = null;
+ String address = null;
+
+ try {
+ String jsonString = info.lookupUri.getEncodedFragment();
+ JSONObject json = new JSONObject(jsonString);
+ JSONObject contact = json.getJSONObject(Contacts.CONTENT_ITEM_TYPE);
+
+ if (!contact.has(StructuredPostal.CONTENT_ITEM_TYPE)) {
+ return null;
+ }
+
+ JSONArray addresses = contact.getJSONArray(
+ StructuredPostal.CONTENT_ITEM_TYPE);
+
+ if (addresses.length() == 0) {
+ return null;
+ }
+
+ JSONObject addressEntry = addresses.getJSONObject(0);
+
+ if (addressEntry.has(StructuredPostal.CITY)) {
+ city = addressEntry.getString(StructuredPostal.CITY);
+ }
+ if (addressEntry.has(StructuredPostal.FORMATTED_ADDRESS)) {
+ address = addressEntry.getString(
+ StructuredPostal.FORMATTED_ADDRESS);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to get address", e);
+ }
+
+ if (city != null) {
+ return city;
+ } else if (address != null) {
+ return address;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Execute thread that is killed after a specified amount of time.
+ *
+ * @param callable The thread
+ * @param name Name of the thread
+ * @return Instance of the thread
+ */
+ private <T> T execute(Callable<T> callable, String name) {
+ FutureCallable<T> futureCallable = new FutureCallable<T>(callable);
+ FutureTask<T> future = new FutureTask<T>(futureCallable);
+ futureCallable.setFuture(future);
+
+ synchronized (mActiveTasks) {
+ mActiveTasks.addLast(future);
+ Log.v(TAG, "Currently running tasks: " + mActiveTasks.size());
+
+ while (mActiveTasks.size() > 8) {
+ Log.w(TAG, "Too many tasks, canceling one");
+ mActiveTasks.removeFirst().cancel(true);
+ }
+ }
+
+ Log.v(TAG, "Starting task " + name);
+
+ new Thread(future, name).start();
+
+ try {
+ Log.v(TAG, "Getting future " + name);
+ return future.get(10000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Task was interrupted: " + name);
+ Thread.currentThread().interrupt();
+ } catch (ExecutionException e) {
+ Log.w(TAG, "Task threw an exception: " + name, e);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Task timed out: " + name);
+ future.cancel(true);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Task was cancelled: " + name);
+ }
+
+ return null;
+ }
+}
diff --git a/java/com/android/dialer/lookup/LookupSettings.java b/java/com/android/dialer/lookup/LookupSettings.java
new file mode 100644
index 0000000..bed395d
--- /dev/null
+++ b/java/com/android/dialer/lookup/LookupSettings.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import lineageos.providers.LineageSettings;
+
+import java.util.List;
+
+public final class LookupSettings {
+ private static final String TAG = LookupSettings.class.getSimpleName();
+
+ /** Forward lookup providers */
+ public static final String FLP_GOOGLE = "Google";
+ public static final String FLP_OPENSTREETMAP = "OpenStreetMap";
+ public static final String FLP_DEFAULT = FLP_GOOGLE;
+
+ /** People lookup providers */
+ public static final String PLP_AUSKUNFT = "Auskunft";
+ public static final String PLP_DEFAULT = PLP_AUSKUNFT;
+
+ /** Reverse lookup providers */
+ public static final String RLP_OPENCNAM = "OpenCnam";
+ public static final String RLP_YELLOWPAGES = "YellowPages";
+ public static final String RLP_YELLOWPAGES_CA = "YellowPages_CA";
+ public static final String RLP_ZABASEARCH = "ZabaSearch";
+ public static final String RLP_CYNGN_CHINESE = "CyngnChinese";
+ public static final String RLP_DASTELEFONBUCH = "DasTelefonbuch";
+ public static final String RLP_AUSKUNFT = "Auskunft";
+ public static final String RLP_DEFAULT = RLP_OPENCNAM;
+
+ private LookupSettings() {
+ }
+
+ public static boolean isForwardLookupEnabled(Context context) {
+ return LineageSettings.System.getInt(context.getContentResolver(),
+ LineageSettings.System.ENABLE_FORWARD_LOOKUP, 1) != 0;
+ }
+
+ public static boolean isPeopleLookupEnabled(Context context) {
+ return LineageSettings.System.getInt(context.getContentResolver(),
+ LineageSettings.System.ENABLE_PEOPLE_LOOKUP, 1) != 0;
+ }
+
+ public static boolean isReverseLookupEnabled(Context context) {
+ return LineageSettings.System.getInt(context.getContentResolver(),
+ LineageSettings.System.ENABLE_REVERSE_LOOKUP, 1) != 0;
+ }
+
+ public static String getForwardLookupProvider(Context context) {
+ String provider = getLookupProvider(context,
+ LineageSettings.System.FORWARD_LOOKUP_PROVIDER, FLP_DEFAULT);
+
+ return provider;
+ }
+
+ public static String getPeopleLookupProvider(Context context) {
+ String provider = getLookupProvider(context,
+ LineageSettings.System.PEOPLE_LOOKUP_PROVIDER, PLP_DEFAULT);
+
+ return provider;
+ }
+
+ public static String getReverseLookupProvider(Context context) {
+ String provider = getLookupProvider(context,
+ LineageSettings.System.REVERSE_LOOKUP_PROVIDER, RLP_DEFAULT);
+
+ if ("Google".equals(provider)) {
+ LineageSettings.System.putString(context.getContentResolver(),
+ LineageSettings.System.REVERSE_LOOKUP_PROVIDER, RLP_DEFAULT);
+ provider = RLP_DEFAULT;
+ }
+
+ return provider;
+ }
+
+ private static String getLookupProvider(Context context,
+ String key, String defaultValue) {
+ ContentResolver cr = context.getContentResolver();
+ String provider = LineageSettings.System.getString(cr, key);
+
+ if (provider == null) {
+ LineageSettings.System.putString(cr, key, defaultValue);
+ return defaultValue;
+ }
+
+ return provider;
+ }
+}
diff --git a/java/com/android/dialer/lookup/LookupSettingsFragment.java b/java/com/android/dialer/lookup/LookupSettingsFragment.java
new file mode 100644
index 0000000..8ec1028
--- /dev/null
+++ b/java/com/android/dialer/lookup/LookupSettingsFragment.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.lookup;
+
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.ListPreference;
+import android.preference.PreferenceFragment;
+import android.preference.SwitchPreference;
+
+import com.android.dialer.R;
+
+import lineageos.providers.LineageSettings;
+
+import java.util.Arrays;
+
+public class LookupSettingsFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_ENABLE_FORWARD_LOOKUP = "enable_forward_lookup";
+ private static final String KEY_ENABLE_PEOPLE_LOOKUP = "enable_people_lookup";
+ private static final String KEY_ENABLE_REVERSE_LOOKUP = "enable_reverse_lookup";
+ private static final String KEY_FORWARD_LOOKUP_PROVIDER = "forward_lookup_provider";
+ private static final String KEY_PEOPLE_LOOKUP_PROVIDER = "people_lookup_provider";
+ private static final String KEY_REVERSE_LOOKUP_PROVIDER = "reverse_lookup_provider";
+
+ private SwitchPreference mEnableForwardLookup;
+ private SwitchPreference mEnablePeopleLookup;
+ private SwitchPreference mEnableReverseLookup;
+ private ListPreference mForwardLookupProvider;
+ private ListPreference mPeopleLookupProvider;
+ private ListPreference mReverseLookupProvider;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.lookup_settings);
+
+ mEnableForwardLookup = (SwitchPreference) findPreference(KEY_ENABLE_FORWARD_LOOKUP);
+ mEnablePeopleLookup = (SwitchPreference) findPreference(KEY_ENABLE_PEOPLE_LOOKUP);
+ mEnableReverseLookup = (SwitchPreference) findPreference(KEY_ENABLE_REVERSE_LOOKUP);
+
+ mEnableForwardLookup.setOnPreferenceChangeListener(this);
+ mEnablePeopleLookup.setOnPreferenceChangeListener(this);
+ mEnableReverseLookup.setOnPreferenceChangeListener(this);
+
+ mForwardLookupProvider = (ListPreference) findPreference(KEY_FORWARD_LOOKUP_PROVIDER);
+ mPeopleLookupProvider = (ListPreference) findPreference(KEY_PEOPLE_LOOKUP_PROVIDER);
+ mReverseLookupProvider = (ListPreference) findPreference(KEY_REVERSE_LOOKUP_PROVIDER);
+
+ mForwardLookupProvider.setOnPreferenceChangeListener(this);
+ mPeopleLookupProvider.setOnPreferenceChangeListener(this);
+ mReverseLookupProvider.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ restoreLookupProviderSwitches();
+ restoreLookupProviders();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final ContentResolver cr = getActivity().getContentResolver();
+
+ if (preference == mEnableForwardLookup) {
+ LineageSettings.System.putInt(cr, LineageSettings.System.ENABLE_FORWARD_LOOKUP,
+ ((Boolean) newValue) ? 1 : 0);
+ } else if (preference == mEnablePeopleLookup) {
+ LineageSettings.System.putInt(cr, LineageSettings.System.ENABLE_PEOPLE_LOOKUP,
+ ((Boolean) newValue) ? 1 : 0);
+ } else if (preference == mEnableReverseLookup) {
+ LineageSettings.System.putInt(cr, LineageSettings.System.ENABLE_REVERSE_LOOKUP,
+ ((Boolean) newValue) ? 1 : 0);
+ } else if (preference == mForwardLookupProvider) {
+ LineageSettings.System.putString(cr, LineageSettings.System.FORWARD_LOOKUP_PROVIDER,
+ (String) newValue);
+ } else if (preference == mPeopleLookupProvider) {
+ LineageSettings.System.putString(cr, LineageSettings.System.PEOPLE_LOOKUP_PROVIDER,
+ (String) newValue);
+ } else if (preference == mReverseLookupProvider) {
+ LineageSettings.System.putString(cr, LineageSettings.System.REVERSE_LOOKUP_PROVIDER,
+ (String) newValue);
+ }
+
+ return true;
+ }
+
+ private void restoreLookupProviderSwitches() {
+ final ContentResolver cr = getActivity().getContentResolver();
+ mEnableForwardLookup.setChecked(LineageSettings.System.getInt(cr,
+ LineageSettings.System.ENABLE_FORWARD_LOOKUP, 1) != 0);
+ mEnablePeopleLookup.setChecked(LineageSettings.System.getInt(cr,
+ LineageSettings.System.ENABLE_PEOPLE_LOOKUP, 1) != 0);
+ mEnableReverseLookup.setChecked(LineageSettings.System.getInt(cr,
+ LineageSettings.System.ENABLE_REVERSE_LOOKUP, 1) != 0);
+ }
+
+ private void restoreLookupProviders() {
+ restoreLookupProvider(mForwardLookupProvider,
+ LineageSettings.System.FORWARD_LOOKUP_PROVIDER);
+ restoreLookupProvider(mPeopleLookupProvider,
+ LineageSettings.System.PEOPLE_LOOKUP_PROVIDER);
+ restoreLookupProvider(mReverseLookupProvider,
+ LineageSettings.System.REVERSE_LOOKUP_PROVIDER);
+ }
+
+ private void restoreLookupProvider(ListPreference pref, String key) {
+ if (pref.getEntries().length < 1) {
+ pref.setEnabled(false);
+ return;
+ }
+
+ final ContentResolver cr = getActivity().getContentResolver();
+ String provider = LineageSettings.System.getString(cr, key);
+ if (provider == null) {
+ pref.setValueIndex(0);
+ LineageSettings.System.putString(cr, key, pref.getValue());
+ } else {
+ pref.setValue(provider);
+ }
+ }
+}
diff --git a/java/com/android/dialer/lookup/LookupUtils.java b/java/com/android/dialer/lookup/LookupUtils.java
new file mode 100644
index 0000000..f13a33e
--- /dev/null
+++ b/java/com/android/dialer/lookup/LookupUtils.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.lookup;
+
+import android.text.Html;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LookupUtils {
+ private static final String USER_AGENT =
+ "Mozilla/5.0 (X11; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0";
+
+ private static HttpURLConnection prepareHttpConnection(String url, Map<String, String> headers)
+ throws IOException {
+ // open connection
+ HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
+ // set user agent (default value is null)
+ urlConnection.setRequestProperty("User-Agent", USER_AGENT);
+ // set all other headers if not null
+ if (headers != null) {
+ for (Map.Entry<String, String> header : headers.entrySet()) {
+ urlConnection.setRequestProperty(header.getKey(), header.getValue());
+ }
+ }
+
+ return urlConnection;
+ }
+
+ private static byte[] httpFetch(HttpURLConnection urlConnection) throws IOException {
+ // query url, read and return buffered response body
+ // we want to make sure that the connection gets closed here
+ InputStream is = new BufferedInputStream(urlConnection.getInputStream());
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] result = null;
+ try {
+ byte[] partial = new byte[4096];
+ int read;
+ while ((read = is.read(partial, 0, 4096)) != -1) {
+ baos.write(partial, 0, read);
+ }
+ result = baos.toByteArray();
+ } finally {
+ is.close();
+ baos.close();
+ }
+ return result;
+ }
+
+ private static Charset determineCharset(HttpURLConnection connection) {
+ String contentType = connection.getContentType();
+ if (contentType != null) {
+ String[] split = contentType.split(";");
+ for (int i = 0; i < split.length; i++) {
+ String trimmed = split[i].trim();
+ if (trimmed.startsWith("charset=")) {
+ try {
+ return Charset.forName(trimmed.substring(8));
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+ // we don't know about this charset -> ignore
+ }
+ }
+ }
+ }
+ return Charset.defaultCharset();
+ }
+
+ public static String httpGet(String url, Map<String, String> headers) throws IOException {
+ HttpURLConnection connection = prepareHttpConnection(url, headers);
+ try {
+ byte[] response = httpFetch(connection);
+ return new String(response, determineCharset(connection));
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ public static byte[] httpGetBytes(String url, Map<String, String> headers) throws IOException {
+ HttpURLConnection connection = prepareHttpConnection(url, headers);
+ try {
+ return httpFetch(connection);
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ public static String httpPost(String url, Map<String, String> headers, String postData)
+ throws IOException {
+ HttpURLConnection connection = prepareHttpConnection(url, headers);
+
+ try {
+ // write postData to buffered output stream
+ if (postData != null) {
+ connection.setDoOutput(true);
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
+ connection.getOutputStream()));
+ try {
+ bw.write(postData, 0, postData.length());
+ // close connection and re-throw exception
+ } finally {
+ bw.close();
+ }
+ }
+ byte[] response = httpFetch(connection);
+ return new String(response, determineCharset(connection));
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ public static List<String> allRegexResults(String input, String regex, boolean dotall) {
+ if (input == null) {
+ return null;
+ }
+ Pattern pattern = Pattern.compile(regex, dotall ? Pattern.DOTALL : 0);
+ Matcher matcher = pattern.matcher(input);
+
+ List<String> regexResults = new ArrayList<String>();
+ while (matcher.find()) {
+ regexResults.add(matcher.group(1).trim());
+ }
+ return regexResults;
+ }
+
+ public static String firstRegexResult(String input, String regex, boolean dotall) {
+ if (input == null) {
+ return null;
+ }
+ Pattern pattern = Pattern.compile(regex, dotall ? Pattern.DOTALL : 0);
+ Matcher m = pattern.matcher(input);
+ return m.find() ? m.group(1).trim() : null;
+ }
+
+ public static String fromHtml(String input) {
+ if (input == null) {
+ return null;
+ }
+ return Html.fromHtml(input).toString().trim();
+ }
+}
diff --git a/java/com/android/dialer/lookup/PeopleLookup.java b/java/com/android/dialer/lookup/PeopleLookup.java
new file mode 100644
index 0000000..28efbed
--- /dev/null
+++ b/java/com/android/dialer/lookup/PeopleLookup.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.auskunft.AuskunftPeopleLookup;
+
+import android.content.Context;
+import android.util.Log;
+
+public abstract class PeopleLookup {
+ private static final String TAG = PeopleLookup.class.getSimpleName();
+
+ private static PeopleLookup INSTANCE = null;
+
+ public static PeopleLookup getInstance(Context context) {
+ String provider = LookupSettings.getPeopleLookupProvider(context);
+
+ if (INSTANCE == null || !isInstance(provider)) {
+ Log.d(TAG, "Chosen people lookup provider: " + provider);
+
+ if (provider.equals(LookupSettings.PLP_AUSKUNFT)) {
+ INSTANCE = new AuskunftPeopleLookup(context);
+ }
+ }
+
+ return INSTANCE;
+ }
+
+ private static boolean isInstance(String provider) {
+ if (provider.equals(LookupSettings.PLP_AUSKUNFT)
+ && INSTANCE instanceof AuskunftPeopleLookup) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public abstract ContactInfo[] lookup(Context context,
+ String filter);
+}
diff --git a/java/com/android/dialer/lookup/ReverseLookup.java b/java/com/android/dialer/lookup/ReverseLookup.java
new file mode 100644
index 0000000..5056926
--- /dev/null
+++ b/java/com/android/dialer/lookup/ReverseLookup.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.auskunft.AuskunftReverseLookup;
+import com.android.dialer.lookup.dastelefonbuch.TelefonbuchReverseLookup;
+import com.android.dialer.lookup.opencnam.OpenCnamReverseLookup;
+import com.android.dialer.lookup.yellowpages.YellowPagesReverseLookup;
+import com.android.dialer.lookup.zabasearch.ZabaSearchReverseLookup;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
+
+public abstract class ReverseLookup {
+ private static final String TAG = ReverseLookup.class.getSimpleName();
+
+ private static ReverseLookup INSTANCE = null;
+
+ public static ReverseLookup getInstance(Context context) {
+ String provider = LookupSettings.getReverseLookupProvider(context);
+
+ if (INSTANCE == null || !isInstance(provider)) {
+ Log.d(TAG, "Chosen reverse lookup provider: " + provider);
+
+ if (provider.equals(LookupSettings.RLP_OPENCNAM)) {
+ INSTANCE = new OpenCnamReverseLookup(context);
+ } else if (provider.equals(LookupSettings.RLP_YELLOWPAGES)
+ || provider.equals(LookupSettings.RLP_YELLOWPAGES_CA)) {
+ INSTANCE = new YellowPagesReverseLookup(context);
+ } else if (provider.equals(LookupSettings.RLP_ZABASEARCH)) {
+ INSTANCE = new ZabaSearchReverseLookup(context);
+ } else if (provider.equals(LookupSettings.RLP_DASTELEFONBUCH)) {
+ INSTANCE = new TelefonbuchReverseLookup(context);
+ } else if (provider.equals(LookupSettings.RLP_AUSKUNFT)) {
+ INSTANCE = new AuskunftReverseLookup(context);
+ }
+ }
+
+ return INSTANCE;
+ }
+
+ private static boolean isInstance(String provider) {
+ if (provider.equals(LookupSettings.RLP_OPENCNAM)
+ && INSTANCE instanceof OpenCnamReverseLookup) {
+ return true;
+ } else if ((provider.equals(LookupSettings.RLP_YELLOWPAGES)
+ || provider.equals(LookupSettings.RLP_YELLOWPAGES_CA))
+ && INSTANCE instanceof YellowPagesReverseLookup) {
+ return true;
+ } else if (provider.equals(LookupSettings.RLP_ZABASEARCH)
+ && INSTANCE instanceof ZabaSearchReverseLookup) {
+ return true;
+ } else if (provider.equals(LookupSettings.RLP_DASTELEFONBUCH)
+ && INSTANCE instanceof TelefonbuchReverseLookup) {
+ return true;
+ } else if (provider.equals(LookupSettings.RLP_AUSKUNFT)
+ && INSTANCE instanceof AuskunftReverseLookup) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Lookup image
+ *
+ * @param context The application context
+ * @param uri The image URI
+ */
+ public Bitmap lookupImage(Context context, Uri uri) {
+ return null;
+ }
+
+ /**
+ * Perform phone number lookup.
+ *
+ * @param context The application context
+ * @param normalizedNumber The normalized phone number
+ * @param formattedNumber The formatted phone number
+ * @return The phone number info object
+ */
+ public abstract ContactInfo lookupNumber(Context context,
+ String normalizedNumber, String formattedNumber) throws IOException;
+}
diff --git a/java/com/android/dialer/lookup/ReverseLookupService.java b/java/com/android/dialer/lookup/ReverseLookupService.java
new file mode 100644
index 0000000..fe4614a
--- /dev/null
+++ b/java/com/android/dialer/lookup/ReverseLookupService.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.logging.ContactLookupResult;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.incallui.bindings.PhoneNumberService;
+
+import java.io.IOException;
+
+public class ReverseLookupService implements PhoneNumberService, Handler.Callback {
+ private final HandlerThread mBackgroundThread;
+ private final Handler mBackgroundHandler;
+ private final Handler mHandler;
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+
+ private static final int MSG_LOOKUP = 1;
+ private static final int MSG_NOTIFY_NUMBER = 2;
+ private static final int MSG_NOTIFY_IMAGE = 3;
+
+ public ReverseLookupService(Context context) {
+ mContext = context;
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+ // TODO: stop after a while?
+ mBackgroundThread = new HandlerThread("ReverseLookup");
+ mBackgroundThread.start();
+
+ mBackgroundHandler = new Handler(mBackgroundThread.getLooper(), this);
+ mHandler = new Handler(this);
+ }
+
+ @Override
+ public void getPhoneNumberInfo(String phoneNumber, NumberLookupListener numberListener,
+ ImageLookupListener imageListener, boolean isIncoming) {
+ if (!LookupSettings.isReverseLookupEnabled(mContext)) {
+ LookupCache.deleteCachedContacts(mContext);
+ return;
+ }
+
+ String countryIso = mTelephonyManager.getSimCountryIso().toUpperCase();
+ String normalizedNumber = phoneNumber != null
+ ? PhoneNumberUtils.formatNumberToE164(phoneNumber, countryIso) : null;
+
+ // Can't do reverse lookup without a number
+ if (normalizedNumber == null) {
+ return;
+ }
+
+ LookupRequest request = new LookupRequest();
+ request.normalizedNumber = normalizedNumber;
+ request.formattedNumber = PhoneNumberUtils.formatNumber(phoneNumber,
+ request.normalizedNumber, GeoUtil.getCurrentCountryIso(mContext));
+ request.numberListener = numberListener;
+ request.imageListener = imageListener;
+
+ mBackgroundHandler.obtainMessage(MSG_LOOKUP, request).sendToTarget();
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOOKUP: {
+ // background thread
+ LookupRequest request = (LookupRequest) msg.obj;
+ request.contactInfo = doLookup(request);
+ if (request.contactInfo != null) {
+ mHandler.obtainMessage(MSG_NOTIFY_NUMBER, request).sendToTarget();
+ if (request.imageListener != null && request.contactInfo.photoUri != null) {
+ request.photo = fetchImage(request, request.contactInfo.photoUri);
+ if (request.photo != null) {
+ mHandler.obtainMessage(MSG_NOTIFY_IMAGE, request).sendToTarget();
+ }
+ }
+ }
+ break;
+ }
+ case MSG_NOTIFY_NUMBER: {
+ // main thread
+ LookupRequest request = (LookupRequest) msg.obj;
+ if (request.numberListener != null) {
+ LookupNumberInfo info = new LookupNumberInfo(request.contactInfo);
+ request.numberListener.onPhoneNumberInfoComplete(info);
+ }
+ break;
+ }
+ case MSG_NOTIFY_IMAGE:
+ // main thread
+ LookupRequest request = (LookupRequest) msg.obj;
+ if (request.imageListener != null) {
+ request.imageListener.onImageFetchComplete(request.photo);
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ private ContactInfo doLookup(LookupRequest request) {
+ final String number = request.normalizedNumber;
+
+ if (LookupCache.hasCachedContact(mContext, number)) {
+ ContactInfo info = LookupCache.getCachedContact(mContext, number);
+ if (!ContactInfo.EMPTY.equals(info)) {
+ return info;
+ } else if (info != null) {
+ // If we have an empty cached contact, remove it and redo lookup
+ LookupCache.deleteCachedContact(mContext, number);
+ }
+ }
+
+ try {
+ ContactInfo info = ReverseLookup.getInstance(mContext).lookupNumber(mContext,
+ number, request.formattedNumber);
+ if (info != null && !info.equals(ContactInfo.EMPTY)) {
+ LookupCache.cacheContact(mContext, info);
+ return info;
+ }
+ } catch (IOException e) {
+ // ignored
+ }
+
+ return null;
+ }
+
+ private Bitmap fetchImage(LookupRequest request, Uri uri) {
+ if (!LookupCache.hasCachedImage(mContext, request.normalizedNumber)) {
+ Bitmap bmp = ReverseLookup.getInstance(mContext).lookupImage(mContext, uri);
+ if (bmp != null) {
+ LookupCache.cacheImage(mContext, request.normalizedNumber, bmp);
+ }
+ }
+
+ return LookupCache.getCachedImage(mContext, request.normalizedNumber);
+ }
+
+ private static class LookupRequest {
+ String normalizedNumber;
+ String formattedNumber;
+ NumberLookupListener numberListener;
+ ImageLookupListener imageListener;
+ ContactInfo contactInfo;
+ Bitmap photo;
+ }
+
+ private static class LookupNumberInfo implements PhoneNumberInfo {
+ private ContactInfo mInfo;
+ private LookupNumberInfo(ContactInfo info) {
+ mInfo = info;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return mInfo.name;
+ }
+ @Override
+ public String getNumber() {
+ return mInfo.number;
+ }
+ @Override
+ public int getPhoneType() {
+ return mInfo.type;
+ }
+ @Override
+ public String getPhoneLabel() {
+ return mInfo.label;
+ }
+ @Override
+ public String getNormalizedNumber() {
+ return mInfo.normalizedNumber;
+ }
+ @Override
+ public String getImageUrl() {
+ return mInfo.photoUri != null ? mInfo.photoUri.toString() : null;
+ }
+ @Override
+ public boolean isBusiness() {
+ // FIXME
+ return false;
+ }
+ @Override
+ public String getLookupKey() {
+ return mInfo.lookupKey;
+ }
+ @Override
+ public ContactLookupResult.Type getLookupSource() {
+ return ContactLookupResult.Type.REMOTE;
+ }
+ }
+}
diff --git a/java/com/android/dialer/lookup/auskunft/AuskunftApi.java b/java/com/android/dialer/lookup/auskunft/AuskunftApi.java
new file mode 100644
index 0000000..4a0a1c0
--- /dev/null
+++ b/java/com/android/dialer/lookup/auskunft/AuskunftApi.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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.lookup.auskunft;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.LookupUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AuskunftApi {
+ private static final String TAG = AuskunftApi.class.getSimpleName();
+
+ private static final String PEOPLE_LOOKUP_URL =
+ "https://auskunft.at/suche";
+
+ private static final String SEARCH_RESULTS_REGEX =
+ "(?i)<section[\\s]+class=[\"']?search-entry(.*?)?</section";
+ private static final String NAME_REGEX =
+ "(?i)<h1[\\s]+itemprop=[\"']?name[\"']?>(.*?)</h1";
+ private static final String NUMBER_REGEX =
+ "(?i)phone[\"'][\\s]+?href=[\"']{1}tel:(.*?)[\"']{1}";
+ private static final String ADDRESS_REGEX =
+ "(?i)<span[\\s]+itemprop=[\"']?streetAddress[\"']?>(.*?)</a";
+
+ private static final String BUSINESS_IDENTIFIER = "(Firma)";
+
+ private AuskunftApi() {
+ }
+
+ public static List<ContactInfo> query(String filter, int lookupType, String normalizedNumber,
+ String formattedNumber) throws IOException {
+ // build URI
+ Uri uri = Uri.parse(PEOPLE_LOOKUP_URL)
+ .buildUpon()
+ .appendQueryParameter("query", filter)
+ .build();
+
+ // get all search entry sections
+ List<String> entries = LookupUtils.allRegexResults(LookupUtils.httpGet(uri.toString(),
+ null), SEARCH_RESULTS_REGEX, true);
+
+ // abort lookup if nothing found
+ if (entries == null || entries.isEmpty()) {
+ Log.w(TAG, "nothing found");
+ return null;
+ }
+
+ // build response by iterating through the search entries and parsing their HTML data
+ List<ContactInfo> infos = new ArrayList<ContactInfo>();
+ for (String entry : entries) {
+ // parse wanted data and replace null values
+ String name = replaceNullResult(LookupUtils.firstRegexResult(entry, NAME_REGEX, true));
+ String address = replaceNullResult(LookupUtils.firstRegexResult(
+ entry, ADDRESS_REGEX, true));
+ String number = replaceNullResult(LookupUtils.firstRegexResult(
+ entry, NUMBER_REGEX, true));
+ // ignore entry if name or number is empty (should not occur)
+ // missing addresses won't be a problem (but do occur)
+ if (name.isEmpty() || number.isEmpty()) {
+ continue;
+ }
+ // figure out if we have a business contact
+ boolean isBusiness = name.contains(BUSINESS_IDENTIFIER);
+ // cleanup results
+ name = cleanupResult(name);
+ number = cleanupResult(number);
+ address = cleanupResult(address);
+ // set normalized and formatted number if we're not doing a reverse lookup
+ if (lookupType != ContactBuilder.REVERSE_LOOKUP) {
+ normalizedNumber = formattedNumber = number;
+ }
+ // build contact and add to list
+ ContactBuilder builder = new ContactBuilder(lookupType, normalizedNumber,
+ formattedNumber);
+ builder.setName(ContactBuilder.Name.createDisplayName(name));
+ builder.addPhoneNumber(
+ ContactBuilder.PhoneNumber.createMainNumber(number));
+ builder.addWebsite(ContactBuilder.WebsiteUrl.createProfile(uri.toString()));
+ builder.addAddress(ContactBuilder.Address.createFormattedHome(address));
+ builder.setIsBusiness(isBusiness);
+ infos.add(builder.build());
+ }
+ return infos;
+ }
+
+ private static String cleanupResult(String result) {
+ // get displayable text
+ result = LookupUtils.fromHtml(result);
+ // replace newlines with spaces
+ result = result.replaceAll("\\r|\\n", " ");
+ // replace multiple spaces with one
+ result = result.replaceAll("\\s+", " ");
+ // remove business identifier that is originally not part of the name
+ result = result.replace(BUSINESS_IDENTIFIER, "");
+ // final trimming
+ result = result.trim();
+
+ return result;
+ }
+
+ private static String replaceNullResult(String result) {
+ return (result == null) ? "" : result;
+ }
+}
diff --git a/java/com/android/dialer/lookup/auskunft/AuskunftPeopleLookup.java b/java/com/android/dialer/lookup/auskunft/AuskunftPeopleLookup.java
new file mode 100644
index 0000000..c51e543
--- /dev/null
+++ b/java/com/android/dialer/lookup/auskunft/AuskunftPeopleLookup.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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.lookup.auskunft;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.PeopleLookup;
+
+import java.io.IOException;
+import java.util.List;
+
+public class AuskunftPeopleLookup extends PeopleLookup {
+ private static final String TAG = AuskunftPeopleLookup.class.getSimpleName();
+
+ public AuskunftPeopleLookup(Context context) {
+ }
+
+ @Override
+ public ContactInfo[] lookup(Context context, String filter) {
+ List<ContactInfo> infos = null;
+ try {
+ infos = AuskunftApi.query(filter, ContactBuilder.PEOPLE_LOOKUP, null, null);
+ } catch (IOException e) {
+ Log.e(TAG, "People lookup failed", e);
+ }
+ return (infos != null && !infos.isEmpty())
+ ? infos.toArray(new ContactInfo[infos.size()]) : null;
+ }
+}
diff --git a/java/com/android/dialer/lookup/auskunft/AuskunftReverseLookup.java b/java/com/android/dialer/lookup/auskunft/AuskunftReverseLookup.java
new file mode 100644
index 0000000..cc046de
--- /dev/null
+++ b/java/com/android/dialer/lookup/auskunft/AuskunftReverseLookup.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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.lookup.auskunft;
+
+import android.content.Context;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ReverseLookup;
+
+import java.io.IOException;
+import java.util.List;
+
+public class AuskunftReverseLookup extends ReverseLookup {
+ private static final String TAG = AuskunftReverseLookup.class.getSimpleName();
+
+ public AuskunftReverseLookup(Context context) {
+ }
+
+ @Override
+ public ContactInfo lookupNumber(Context context, String normalizedNumber,
+ String formattedNumber) throws IOException {
+ // only Austrian numbers are supported
+ if (normalizedNumber.startsWith("+") && !normalizedNumber.startsWith("+43")) {
+ return null;
+ }
+
+ // query the API and return null if nothing found or general error
+ List<ContactInfo> infos = AuskunftApi.query(normalizedNumber, ContactBuilder.REVERSE_LOOKUP,
+ normalizedNumber, formattedNumber);
+ return (infos != null && !infos.isEmpty()) ? infos.get(0) : null;
+ }
+}
diff --git a/java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java b/java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java
new file mode 100644
index 0000000..ff2fd4b
--- /dev/null
+++ b/java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 Danny Baumann <dannybaumann@web.de>
+ *
+ * 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.lookup.dastelefonbuch;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.dialer.lookup.LookupUtils;
+
+import java.io.IOException;
+
+public class TelefonbuchApi {
+ private static final String TAG = TelefonbuchApi.class.getSimpleName();
+
+ private static final String REVERSE_LOOKUP_URL =
+ "https://www.dastelefonbuch.de/?s=a20000" +
+ "&cmd=search&sort_ok=0&sp=55&vert_ok=0&aktion=23";
+
+ private static String NAME_REGEX ="<a id=\"name0.*?>\\s*\n?(.*?)\n?\\s*</a>";
+ private static String NUMBER_REGEX = "<span\\s+class=\"ico fon.*>.*<span>(.*?)</span><br/>";
+ private static String ADDRESS_REGEX = "<address.*?>\n?(.*?)</address>";
+
+ private TelefonbuchApi() {
+ }
+
+ public static ContactInfo reverseLookup(Context context, String number)
+ throws IOException {
+ Uri uri = Uri.parse(REVERSE_LOOKUP_URL)
+ .buildUpon()
+ .appendQueryParameter("kw", number)
+ .build();
+ // Cut out everything we're not interested in (scripts etc.) to
+ // speed up the subsequent matching.
+ String output = LookupUtils.firstRegexResult(
+ LookupUtils.httpGet(uri.toString(), null),
+ ": Treffer(.*)Ende Treffer", true);
+
+ String name = parseValue(output, NAME_REGEX, true, false);
+ if (name == null) {
+ return null;
+ }
+
+ String phoneNumber = parseValue(output, NUMBER_REGEX, false, true);
+ String address = parseValue(output, ADDRESS_REGEX, true, true);
+
+ ContactInfo info = new ContactInfo();
+ info.name = name;
+ info.address = address;
+ info.formattedNumber = phoneNumber != null ? phoneNumber : number;
+ info.website = uri.toString();
+
+ return info;
+ }
+
+ private static String parseValue(String output, String regex,
+ boolean dotall, boolean removeSpans) {
+ String result = LookupUtils.firstRegexResult(output, regex, dotall);
+ if (result != null && removeSpans) {
+ // completely remove hidden spans (including contents) ...
+ result = result.replaceAll("<span class=\"hide\".*?\\/span>", "");
+ // ... and remove span wrappers around data content
+ result = result.replaceAll("</?span.*?>", "");
+ }
+ return LookupUtils.fromHtml(result);
+ }
+
+ public static class ContactInfo {
+ String name;
+ String address;
+ String formattedNumber;
+ String website;
+ }
+}
diff --git a/java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java b/java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java
new file mode 100644
index 0000000..88c159c
--- /dev/null
+++ b/java/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 Danny Baumann <dannybaumann@web.de>
+ *
+ * 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.lookup.dastelefonbuch;
+
+import android.content.Context;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ReverseLookup;
+
+import java.io.IOException;
+
+public class TelefonbuchReverseLookup extends ReverseLookup {
+ private static final String TAG = TelefonbuchReverseLookup.class.getSimpleName();
+
+ public TelefonbuchReverseLookup(Context context) {
+ }
+
+ /**
+ * Perform phone number lookup.
+ *
+ * @param context The application context
+ * @param normalizedNumber The normalized phone number
+ * @param formattedNumber The formatted phone number
+ * @return The phone number info object
+ */
+ public ContactInfo lookupNumber(Context context,
+ String normalizedNumber, String formattedNumber) throws IOException {
+ if (normalizedNumber.startsWith("+") && !normalizedNumber.startsWith("+49")) {
+ // Das Telefonbuch only supports German numbers
+ return null;
+ }
+
+ TelefonbuchApi.ContactInfo info = TelefonbuchApi.reverseLookup(context, normalizedNumber);
+ if (info == null) {
+ return null;
+ }
+
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.REVERSE_LOOKUP,
+ normalizedNumber, formattedNumber);
+ builder.setName(ContactBuilder.Name.createDisplayName(info.name));
+ builder.addPhoneNumber(ContactBuilder.PhoneNumber.createMainNumber(info.formattedNumber));
+ builder.addWebsite(ContactBuilder.WebsiteUrl.createProfile(info.website));
+ if (info.address != null) {
+ builder.addAddress(ContactBuilder.Address.createFormattedHome(info.address));
+ }
+
+ return builder.build();
+ }
+}
diff --git a/java/com/android/dialer/lookup/google/GoogleForwardLookup.java b/java/com/android/dialer/lookup/google/GoogleForwardLookup.java
new file mode 100644
index 0000000..fb19ec3
--- /dev/null
+++ b/java/com/android/dialer/lookup/google/GoogleForwardLookup.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup.google;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ForwardLookup;
+import com.android.dialer.lookup.LookupUtils;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.Html;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class GoogleForwardLookup extends ForwardLookup {
+ private static final String TAG =
+ GoogleForwardLookup.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final String QUERY_FILTER = "q";
+ private static final String QUERY_LANGUAGE = "hl";
+ private static final String QUERY_LOCATION = "sll";
+ private static final String QUERY_RADIUS = "radius";
+ private static final String QUERY_RANDOM = "gs_gbg";
+
+ private static final String RESULT_ADDRESS = "a";
+ private static final String RESULT_NUMBER = "b";
+ private static final String RESULT_DISTANCE = "c";
+ private static final String RESULT_PHOTO_URI = "d";
+ private static final String RESULT_WEBSITE = "f";
+ private static final String RESULT_CITY = "g";
+
+ /** Base for the query URL */
+ private static final String LOOKUP_URL =
+ "https://www.google.com/complete/search?gs_ri=dialer";
+
+ /** Minimum query length
+ * (default for dialer_nearby_places_min_query_len) */
+ private static final int MIN_QUERY_LEN = 2;
+
+ /** Maximum query length
+ * (default for dialer_nearby_places_max_query_len) */
+ private static final int MAX_QUERY_LEN = 50;
+
+ /** Radius (in miles)
+ * (default for dialer_nearby_places_directory_radius_meters) */
+ private static final int RADIUS = 1000;
+
+ /** User agent string */
+ private String mUserAgent = "";
+
+ public GoogleForwardLookup(Context context) {
+ StringBuilder sb = new StringBuilder("GoogleDialer ");
+ try {
+ sb.append(context.getPackageManager().getPackageInfo(
+ context.getPackageName(), 0).versionName);
+ sb.append(" ");
+ sb.append(Build.FINGERPRINT);
+ mUserAgent = sb.toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ @Override
+ public ContactInfo[] lookup(Context context,
+ String filter, Location lastLocation) {
+ int length = filter.length();
+
+ if (length >= MIN_QUERY_LEN) {
+ if (length > MAX_QUERY_LEN) {
+ filter = filter.substring(0, MAX_QUERY_LEN);
+ }
+
+ try {
+ Uri.Builder builder = Uri.parse(LOOKUP_URL).buildUpon();
+
+ // Query string
+ builder = builder.appendQueryParameter(QUERY_FILTER, filter);
+
+ // Language
+ builder = builder.appendQueryParameter(QUERY_LANGUAGE,
+ context.getResources().getConfiguration()
+ .locale.getLanguage());
+
+ // Location (latitude and longitude)
+ builder = builder.appendQueryParameter(QUERY_LOCATION,
+ String.format("%f,%f",
+ lastLocation.getLatitude(),
+ lastLocation.getLongitude()));
+
+ // Radius distance
+ builder = builder.appendQueryParameter(QUERY_RADIUS,
+ Integer.toString(RADIUS));
+
+ // Random string (not really required)
+ builder = builder.appendQueryParameter(QUERY_RANDOM,
+ getRandomNoiseString());
+
+ Map<String, String> headers = new HashMap<String, String>();
+ headers.put("User-Agent", mUserAgent);
+ JSONArray results = new JSONArray(LookupUtils.httpGet(builder.build().toString(),
+ headers));
+
+ if (DEBUG) Log.v(TAG, "Results: " + results);
+
+ return getEntries(results);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to execute query", e);
+ } catch (JSONException e) {
+ Log.e(TAG, "JSON error", e);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Parse JSON results and return them as an array of ContactInfo
+ *
+ * @param results The JSON results returned from the server
+ * @return Array of ContactInfo containing the result information
+ */
+ private ContactInfo[] getEntries(JSONArray results)
+ throws JSONException {
+ ArrayList<ContactInfo> details =
+ new ArrayList<ContactInfo>();
+
+ JSONArray entries = results.getJSONArray(1);
+
+ for (int i = 0; i < entries.length(); i++) {
+ try {
+ JSONArray entry = entries.getJSONArray(i);
+
+ String displayName = decodeHtml(entry.getString(0));
+
+ JSONObject params = entry.getJSONObject(3);
+
+ String phoneNumber = decodeHtml(
+ params.getString(RESULT_NUMBER));
+
+ String address = decodeHtml(params.getString(RESULT_ADDRESS));
+ String city = decodeHtml(params.getString(RESULT_CITY));
+
+ String profileUrl = params.optString(RESULT_WEBSITE, null);
+ String photoUri = params.optString(RESULT_PHOTO_URI, null);
+
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.FORWARD_LOOKUP, null, phoneNumber);
+ builder.setName(ContactBuilder.Name.createDisplayName(displayName));
+ builder.addPhoneNumber(ContactBuilder.PhoneNumber.createMainNumber(phoneNumber));
+ builder.addWebsite(ContactBuilder.WebsiteUrl.createProfile(profileUrl));
+
+ ContactBuilder.Address a = new ContactBuilder.Address();
+ a.formattedAddress = address;
+ a.city = city;
+ a.type = StructuredPostal.TYPE_WORK;
+ builder.addAddress(a);
+
+ if (photoUri != null) {
+ builder.setPhotoUri(photoUri);
+ } else {
+ builder.setPhotoUri(ContactBuilder.PHOTO_URI_BUSINESS);
+ }
+
+ details.add(builder.build());
+ } catch (JSONException e) {
+ Log.e(TAG, "Skipping the suggestions at index " + i, e);
+ }
+ }
+
+ if (details.size() > 0) {
+ return details.toArray(new ContactInfo[details.size()]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Generate a random string of alphanumeric characters of length [4, 36)
+ *
+ * @return Random alphanumeric string
+ */
+ private String getRandomNoiseString() {
+ StringBuilder garbage = new StringBuilder();
+
+ int length = getRandomInteger(32) + 4;
+
+ for (int i = 0; i < length; i++) {
+ int asciiCode;
+
+ if (Math.random() >= 0.3) {
+ if (Math.random() <= 0.5) {
+ // Lowercase letters
+ asciiCode = getRandomInteger(26) + 97;
+ } else {
+ // Uppercase letters
+ asciiCode = getRandomInteger(26) + 65;
+ }
+ } else {
+ // Numbers
+ asciiCode = getRandomInteger(10) + 48;
+ }
+
+ garbage.append(Character.toString((char) asciiCode));
+ }
+
+ return garbage.toString();
+ }
+
+ /**
+ * Generate number in the range [0, max).
+ *
+ * @param max Upper limit (non-inclusive)
+ * @return Random number inside [0, max)
+ */
+ private int getRandomInteger(int max) {
+ return (int) Math.floor(Math.random() * max);
+ }
+
+ /**
+ * Convert HTML to unformatted plain text.
+ *
+ * @param s HTML content
+ * @return Unformatted plain text
+ */
+ private String decodeHtml(String s) {
+ return Html.fromHtml(s).toString();
+ }
+}
diff --git a/java/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java b/java/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java
new file mode 100644
index 0000000..0e379d4
--- /dev/null
+++ b/java/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup.opencnam;
+
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.LookupUtils;
+import com.android.dialer.lookup.ReverseLookup;
+
+import lineageos.providers.LineageSettings;
+
+import java.io.IOException;
+
+public class OpenCnamReverseLookup extends ReverseLookup {
+ private static final String TAG =
+ OpenCnamReverseLookup.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final String LOOKUP_URL =
+ "https://api.opencnam.com/v2/phone/";
+
+ /** Query parameters for paid accounts */
+ private static final String ACCOUNT_SID = "account_sid";
+ private static final String AUTH_TOKEN = "auth_token";
+
+ public OpenCnamReverseLookup(Context context) {
+ }
+
+ /**
+ * Perform phone number lookup.
+ *
+ * @param context The application context
+ * @param normalizedNumber The normalized phone number
+ * @param formattedNumber The formatted phone number
+ * @return The phone number info object
+ */
+ public ContactInfo lookupNumber(Context context,
+ String normalizedNumber, String formattedNumber) throws IOException {
+ if (normalizedNumber.startsWith("+") && !normalizedNumber.startsWith("+1")) {
+ // Any non-US number will return "We currently accept only US numbers"
+ return null;
+ }
+
+ String displayName = httpGetRequest(context, normalizedNumber);
+ if (DEBUG) Log.d(TAG, "Reverse lookup returned name: " + displayName);
+
+ // Check displayName. The free tier of the service will return the
+ // following for some numbers:
+ // "CNAM for phone "NORMALIZED" is currently unavailable for Hobbyist Tier users."
+
+ if (displayName.contains("Hobbyist Tier")) {
+ return null;
+ }
+
+ String number = formattedNumber != null
+ ? formattedNumber : normalizedNumber;
+
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.REVERSE_LOOKUP,
+ normalizedNumber, formattedNumber);
+ builder.setName(ContactBuilder.Name.createDisplayName(displayName));
+ builder.addPhoneNumber(ContactBuilder.PhoneNumber.createMainNumber(number));
+ builder.setPhotoUri(ContactBuilder.PHOTO_URI_BUSINESS);
+
+ return builder.build();
+ }
+
+ private String httpGetRequest(Context context, String number) throws IOException {
+ Uri.Builder builder = Uri.parse(LOOKUP_URL + number).buildUpon();
+
+ // Paid account
+ String accountSid = LineageSettings.System.getString(
+ context.getContentResolver(),
+ LineageSettings.System.DIALER_OPENCNAM_ACCOUNT_SID);
+ String authToken = LineageSettings.System.getString(
+ context.getContentResolver(),
+ LineageSettings.System.DIALER_OPENCNAM_AUTH_TOKEN);
+
+ if (!TextUtils.isEmpty(accountSid) && !TextUtils.isEmpty(authToken)) {
+ Log.d(TAG, "Using paid account");
+
+ builder.appendQueryParameter(ACCOUNT_SID, accountSid);
+ builder.appendQueryParameter(AUTH_TOKEN, authToken);
+ }
+
+ return LookupUtils.httpGet(builder.build().toString(), null);
+ }
+}
diff --git a/java/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java b/java/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java
new file mode 100644
index 0000000..72ca04f
--- /dev/null
+++ b/java/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2014 The OmniROM Project
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.
+ */
+
+// Partially based on OmniROM's implementation
+
+package com.android.dialer.lookup.openstreetmap;
+
+import android.content.Context;
+import android.location.Location;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.util.Log;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ForwardLookup;
+import com.android.dialer.lookup.LookupUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class OpenStreetMapForwardLookup extends ForwardLookup {
+ private static final String TAG =
+ OpenStreetMapForwardLookup.class.getSimpleName();
+
+ /** Search within radius (meters) */
+ private static final int RADIUS = 30000;
+
+ /** Query URL */
+ private static final String LOOKUP_URL =
+ "https://overpass-api.de/api/interpreter";
+
+ private static final String LOOKUP_QUERY =
+ "[out:json];node[name~\"%s\"][phone](around:%d,%f,%f);out body;";
+
+ private static final String RESULT_ELEMENTS = "elements";
+ private static final String RESULT_TAGS = "tags";
+ private static final String TAG_NAME = "name";
+ private static final String TAG_PHONE = "phone";
+ private static final String TAG_HOUSENUMBER = "addr:housenumber";
+ private static final String TAG_STREET = "addr:street";
+ private static final String TAG_CITY = "addr:city";
+ private static final String TAG_POSTCODE = "addr:postcode";
+ private static final String TAG_WEBSITE = "website";
+
+ public OpenStreetMapForwardLookup(Context context) {
+ }
+
+ @Override
+ public ContactInfo[] lookup(Context context,
+ String filter, Location lastLocation) {
+
+ // The OSM API doesn't support case-insentive searches, but does
+ // support regular expressions.
+ String regex = "";
+ for (int i = 0; i < filter.length(); i++) {
+ char c = filter.charAt(i);
+ regex += "[" + Character.toUpperCase(c)
+ + Character.toLowerCase(c) + "]";
+ }
+
+ String request = String.format(Locale.ENGLISH, LOOKUP_QUERY, regex,
+ RADIUS, lastLocation.getLatitude(), lastLocation.getLongitude());
+
+ try {
+ return getEntries(new JSONObject(LookupUtils.httpPost(LOOKUP_URL, null, request)));
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to execute query", e);
+ } catch (JSONException e) {
+ Log.e(TAG, "JSON error", e);
+ }
+
+ return null;
+ }
+
+ private ContactInfo[] getEntries(JSONObject results)
+ throws JSONException {
+ ArrayList<ContactInfo> details =
+ new ArrayList<ContactInfo>();
+
+ JSONArray elements = results.getJSONArray(RESULT_ELEMENTS);
+
+ for (int i = 0; i < elements.length(); i++) {
+ try {
+ JSONObject element = elements.getJSONObject(i);
+ JSONObject tags = element.getJSONObject(RESULT_TAGS);
+
+ String displayName = tags.getString(TAG_NAME);
+ String phoneNumber = tags.getString(TAG_PHONE);
+
+ // Take the first number if there are multiple
+ if (phoneNumber.contains(";")) {
+ phoneNumber = phoneNumber.split(";")[0];
+ phoneNumber = phoneNumber.trim();
+ }
+
+ // The address is split
+ String addressHouseNumber =
+ tags.optString(TAG_HOUSENUMBER, null);
+ String addressStreet = tags.optString(TAG_STREET, null);
+ String addressCity = tags.optString(TAG_CITY, null);
+ String addressPostCode = tags.optString(TAG_POSTCODE, null);
+
+ String address = String.format(
+ "%s %s, %s %s",
+ addressHouseNumber != null ? addressHouseNumber : "",
+ addressStreet != null ? addressStreet : "",
+ addressCity != null ? addressCity : "",
+ addressPostCode != null ? addressPostCode : "");
+
+ address = address.trim().replaceAll("\\s+", " ");
+
+ if (address.length() == 0) {
+ address = null;
+ }
+
+ String website = tags.optString(TAG_WEBSITE, null);
+
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.FORWARD_LOOKUP, null, phoneNumber);
+
+ builder.setName(ContactBuilder.Name.createDisplayName(displayName));
+ builder.addPhoneNumber(ContactBuilder.PhoneNumber.createMainNumber(phoneNumber));
+
+ ContactBuilder.Address a = new ContactBuilder.Address();
+ a.formattedAddress = address;
+ a.city = addressCity;
+ a.street = addressStreet;
+ a.postCode = addressPostCode;
+ a.type = StructuredPostal.TYPE_WORK;
+ builder.addAddress(a);
+
+ ContactBuilder.WebsiteUrl w = new ContactBuilder.WebsiteUrl();
+ w.url = website;
+ w.type = Website.TYPE_HOMEPAGE;
+ builder.addWebsite(w);
+
+ builder.setPhotoUri(ContactBuilder.PHOTO_URI_BUSINESS);
+
+ details.add(builder.build());
+ } catch (JSONException e) {
+ Log.e(TAG, "Skipping the suggestions at index " + i, e);
+ }
+ }
+
+ if (details.size() > 0) {
+ return details.toArray(new ContactInfo[details.size()]);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_180_holo_light.png b/java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_180_holo_light.png
new file mode 100644
index 0000000..f0bbe73
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_180_holo_light.png
Binary files differ
diff --git a/java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_holo_light.png b/java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_holo_light.png
new file mode 100644
index 0000000..f70e8e7
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/drawable-hdpi/ic_places_picture_holo_light.png
Binary files differ
diff --git a/java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_180_holo_light.png b/java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_180_holo_light.png
new file mode 100644
index 0000000..6409ab1
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_180_holo_light.png
Binary files differ
diff --git a/java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_holo_light.png b/java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_holo_light.png
new file mode 100644
index 0000000..7c92a60
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/drawable-xhdpi/ic_places_picture_holo_light.png
Binary files differ
diff --git a/java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_180_holo_light.png b/java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_180_holo_light.png
new file mode 100644
index 0000000..97b9822
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_180_holo_light.png
Binary files differ
diff --git a/java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_holo_light.png b/java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_holo_light.png
new file mode 100644
index 0000000..43029bd
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/drawable-xxhdpi/ic_places_picture_holo_light.png
Binary files differ
diff --git a/java/com/android/dialer/lookup/res/values/cm_arrays.xml b/java/com/android/dialer/lookup/res/values/cm_arrays.xml
new file mode 100644
index 0000000..a566727
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/values/cm_arrays.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The CyanogenMod 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="forward_lookup_providers" translatable="false">
+ <item>Google</item>
+ <item>OpenStreetMap</item>
+ </string-array>
+
+ <string-array name="forward_lookup_provider_names" translatable="false">
+ <item>Google</item>
+ <item>OpenStreetMap</item>
+ </string-array>
+
+ <string-array name="people_lookup_providers" translatable="false">
+ <item>Auskunft</item>
+ </string-array>
+
+ <string-array name="people_lookup_provider_names" translatable="false">
+ <item>Auskunft (AT)</item>
+ </string-array>
+
+ <string-array name="reverse_lookup_providers" translatable="false">
+ <item>Auskunft</item>
+ <item>DasTelefonbuch</item>
+ <item>OpenCnam</item>
+ <item>YellowPages</item>
+ <item>YellowPages_CA</item>
+ <item>ZabaSearch</item>
+ </string-array>
+
+ <string-array name="reverse_lookup_provider_names" translatable="false">
+ <item>Auskunft (AT)</item>
+ <item>Das Telefonbuch (DE)</item>
+ <item>OpenCnam (US)</item>
+ <item>YellowPages (US)</item>
+ <item>YellowPages (CA)</item>
+ <item>ZabaSearch (US)</item>
+ </string-array>
+</resources>
diff --git a/java/com/android/dialer/lookup/res/values/cm_strings.xml b/java/com/android/dialer/lookup/res/values/cm_strings.xml
new file mode 100644
index 0000000..28af844
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/values/cm_strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013-2014 The CyanogenMod 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Forward lookup -->
+ <string name="nearby_places">Nearby places</string>
+ <string name="people">People</string>
+
+ <!-- Number lookup -->
+ <string name="lookup_settings_label">Phone number lookup</string>
+ <string name="enable_forward_lookup_title">Forward lookup</string>
+ <string name="enable_forward_lookup_summary">Show nearby places when searching in the dialer</string>
+ <string name="enable_people_lookup_title">People lookup</string>
+ <string name="enable_people_lookup_summary">Show online results for people when searching in the dialer</string>
+ <string name="enable_reverse_lookup_title">Reverse lookup</string>
+ <string name="enable_reverse_lookup_summary">Look up information about the person or place for unknown numbers on incoming calls</string>
+ <string name="forward_lookup_provider_title">Forward lookup provider</string>
+ <string name="people_lookup_provider_title">People lookup provider</string>
+ <string name="reverse_lookup_provider_title">Reverse lookup provider</string>
+</resources>
diff --git a/java/com/android/dialer/lookup/res/xml/lookup_settings.xml b/java/com/android/dialer/lookup/res/xml/lookup_settings.xml
new file mode 100644
index 0000000..5149b63
--- /dev/null
+++ b/java/com/android/dialer/lookup/res/xml/lookup_settings.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2014 The CyanogenMod 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
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <SwitchPreference
+ android:key="enable_forward_lookup"
+ android:title="@string/enable_forward_lookup_title"
+ android:summary="@string/enable_forward_lookup_summary"
+ android:defaultValue="false"
+ android:persistent="false" />
+
+ <ListPreference
+ android:key="forward_lookup_provider"
+ android:title="@string/forward_lookup_provider_title"
+ android:entries="@array/forward_lookup_provider_names"
+ android:entryValues="@array/forward_lookup_providers"
+ android:dependency="enable_forward_lookup"
+ android:summary="%s"
+ android:persistent="false" />
+
+ <SwitchPreference
+ android:key="enable_people_lookup"
+ android:title="@string/enable_people_lookup_title"
+ android:summary="@string/enable_people_lookup_summary"
+ android:defaultValue="false"
+ android:persistent="false" />
+
+ <ListPreference
+ android:key="people_lookup_provider"
+ android:title="@string/people_lookup_provider_title"
+ android:entries="@array/people_lookup_provider_names"
+ android:entryValues="@array/people_lookup_providers"
+ android:summary="%s"
+ android:dependency="enable_people_lookup"
+ android:persistent="false" />
+
+ <SwitchPreference
+ android:key="enable_reverse_lookup"
+ android:title="@string/enable_reverse_lookup_title"
+ android:summary="@string/enable_reverse_lookup_summary"
+ android:defaultValue="false"
+ android:persistent="false" />
+
+ <ListPreference
+ android:key="reverse_lookup_provider"
+ android:title="@string/reverse_lookup_provider_title"
+ android:entries="@array/reverse_lookup_provider_names"
+ android:entryValues="@array/reverse_lookup_providers"
+ android:dependency="enable_reverse_lookup"
+ android:summary="%s"
+ android:persistent="false" />
+
+</PreferenceScreen>
+
diff --git a/java/com/android/dialer/lookup/yellowpages/YellowPagesApi.java b/java/com/android/dialer/lookup/yellowpages/YellowPagesApi.java
new file mode 100644
index 0000000..01f017b
--- /dev/null
+++ b/java/com/android/dialer/lookup/yellowpages/YellowPagesApi.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup.yellowpages;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.android.dialer.lookup.LookupSettings;
+import com.android.dialer.lookup.LookupUtils;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class YellowPagesApi {
+ private static final String TAG = YellowPagesApi.class.getSimpleName();
+
+ private static final String LOOKUP_URL_UNITED_STATES =
+ "https://www.yellowpages.com/phone?phone_search_terms=";
+ private static final String LOOKUP_URL_CANADA =
+ "https://www.yellowpages.ca/search/si/1/";
+
+ private String mProvider = null;
+ private String mNumber = null;
+ private String mOutput = null;
+ private ContactInfo mInfo = null;
+ private String mLookupUrl = null;
+
+ public YellowPagesApi(Context context, String number) {
+ mProvider = LookupSettings.getReverseLookupProvider(context);
+ mNumber = number;
+
+ if (mProvider.equals(LookupSettings.RLP_YELLOWPAGES)) {
+ mLookupUrl = LOOKUP_URL_UNITED_STATES;
+ } else if (mProvider.equals(LookupSettings.RLP_YELLOWPAGES_CA)) {
+ mLookupUrl = LOOKUP_URL_CANADA;
+ }
+ }
+
+ private void fetchPage() throws IOException {
+ mOutput = LookupUtils.httpGet(mLookupUrl + mNumber, null);
+ }
+
+ private String getPhotoUrl(String website) throws IOException {
+ String output = LookupUtils.httpGet(website, null);
+ String galleryRef = LookupUtils.firstRegexResult(output,
+ "href=\"([^\"]+gallery\\?lid=[^\"]+)\"", true);
+ if (galleryRef == null) {
+ return null;
+ }
+
+ // Get first image
+ return LookupUtils.firstRegexResult(
+ LookupUtils.httpGet("https://www.yellowpages.com" + galleryRef, null),
+ "\"type\":\"image\",\"src\":\"([^\"]+)\"", true);
+ }
+
+ private String[] parseNameWebsiteUnitedStates() {
+ Pattern regexNameAndWebsite = Pattern.compile(
+ "<a href=\"([^>]+?)\"[^>]+?class=\"url[^>]+?>([^<]+)</a>",
+ Pattern.DOTALL);
+ String name = null;
+ String website = null;
+
+ Matcher m = regexNameAndWebsite.matcher(mOutput);
+ if (m.find()) {
+ website = m.group(1).trim();
+ name = m.group(2).trim();
+ }
+
+ return new String[] { name, website };
+ }
+
+ private String[] parseNameWebsiteCanada() {
+ Pattern regexNameAndWebsite = Pattern.compile(
+ "class=\"ypgListingTitleLink utagLink\".*?href=\"(.*?)\">"
+ + "(<span\\s+class=\"listingTitle\">.*?</span>)",
+ Pattern.DOTALL);
+ String name = null;
+ String website = null;
+
+ Matcher m = regexNameAndWebsite.matcher(mOutput);
+ if (m.find()) {
+ website = m.group(1).trim();
+ name = LookupUtils.fromHtml(m.group(2).trim());
+ }
+
+ if (website != null) {
+ website = "https://www.yellowpages.ca" + website;
+ }
+
+ return new String[] { name, website };
+ }
+
+ private String parseNumberUnitedStates() {
+ return LookupUtils.firstRegexResult(mOutput,
+ "business-phone.*?>\n*([^\n<]+)\n*<", true);
+ }
+
+ private String parseNumberCanada() {
+ return LookupUtils.firstRegexResult(mOutput,
+ "<div\\s+class=\"phoneNumber\">(.*?)</div>", true);
+ }
+
+ private String parseAddressUnitedStates() {
+ String addressStreet = LookupUtils.firstRegexResult(mOutput,
+ "street-address.*?>\n*([^\n<]+)\n*<", true);
+ if (addressStreet != null && addressStreet.endsWith(",")) {
+ addressStreet = addressStreet.substring(0, addressStreet.length() - 1);
+ }
+
+ String addressCity = LookupUtils.firstRegexResult(mOutput,
+ "locality.*?>\n*([^\n<]+)\n*<", true);
+ String addressState = LookupUtils.firstRegexResult(mOutput,
+ "region.*?>\n*([^\n<]+)\n*<", true);
+ String addressZip = LookupUtils.firstRegexResult(mOutput,
+ "postal-code.*?>\n*([^\n<]+)\n*<", true);
+
+ StringBuilder sb = new StringBuilder();
+
+ if (!TextUtils.isEmpty(addressStreet)) {
+ sb.append(addressStreet);
+ }
+ if (!TextUtils.isEmpty(addressCity)) {
+ sb.append(", ");
+ sb.append(addressCity);
+ }
+ if (!TextUtils.isEmpty(addressState)) {
+ sb.append(", ");
+ sb.append(addressState);
+ }
+ if (!TextUtils.isEmpty(addressZip)) {
+ sb.append(", ");
+ sb.append(addressZip);
+ }
+
+ String address = sb.toString();
+ if (address.length() == 0) {
+ address = null;
+ }
+
+ return address;
+ }
+
+ private String parseAddressCanada() {
+ String address = LookupUtils.firstRegexResult(mOutput,
+ "<div\\s+class=\"address\">(.*?)</div>", true);
+ return LookupUtils.fromHtml(address);
+ }
+
+ private void buildContactInfo() throws IOException {
+ Matcher m;
+
+ String name = null;
+ String website = null;
+ String phoneNumber = null;
+ String address = null;
+ String photoUrl = null;
+
+ if (mProvider.equals(LookupSettings.RLP_YELLOWPAGES)) {
+ String[] ret = parseNameWebsiteUnitedStates();
+ name = ret[0];
+ website = ret[1];
+ phoneNumber = parseNumberUnitedStates();
+ address = parseAddressUnitedStates();
+ if (website != null) {
+ photoUrl = getPhotoUrl(website);
+ }
+ } else if (mProvider.equals(LookupSettings.RLP_YELLOWPAGES_CA)) {
+ String[] ret = parseNameWebsiteCanada();
+ name = ret[0];
+ website = ret[1];
+ phoneNumber = parseNumberCanada();
+ address = parseAddressCanada();
+ // AFAIK, Canada's YellowPages doesn't have photos
+ }
+
+ ContactInfo info = new ContactInfo();
+ info.name = name;
+ info.address = address;
+ info.formattedNumber = phoneNumber != null ? phoneNumber : mNumber;
+ info.website = website;
+ info.photoUrl = photoUrl;
+ mInfo = info;
+ }
+
+ public ContactInfo getContactInfo() throws IOException {
+ if (mInfo == null) {
+ fetchPage();
+
+ buildContactInfo();
+ }
+
+ return mInfo;
+ }
+
+ public static class ContactInfo {
+ String name;
+ String address;
+ String formattedNumber;
+ String website;
+ String photoUrl;
+ }
+}
diff --git a/java/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java b/java/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java
new file mode 100644
index 0000000..4f7eb24
--- /dev/null
+++ b/java/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup.yellowpages;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.LookupUtils;
+import com.android.dialer.lookup.ReverseLookup;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class YellowPagesReverseLookup extends ReverseLookup {
+ private static final String TAG =
+ YellowPagesReverseLookup.class.getSimpleName();
+
+ public YellowPagesReverseLookup(Context context) {
+ }
+
+ /**
+ * Lookup image
+ *
+ * @param context The application context
+ * @param uri The image URI
+ */
+ public Bitmap lookupImage(Context context, Uri uri) {
+ if (uri == null) {
+ throw new NullPointerException("URI is null");
+ }
+
+ Log.e(TAG, "Fetching " + uri);
+
+ String scheme = uri.getScheme();
+
+ if (scheme.startsWith("http")) {
+ try {
+ byte[] response = LookupUtils.httpGetBytes(uri.toString(), null);
+ return BitmapFactory.decodeByteArray(response, 0, response.length);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to retrieve image", e);
+ }
+ } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+ try {
+ ContentResolver cr = context.getContentResolver();
+ Bitmap bmp = BitmapFactory.decodeStream(cr.openInputStream(uri));
+ return bmp;
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to retrieve image", e);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Perform phone number lookup.
+ *
+ * @param context The application context
+ * @param normalizedNumber The normalized phone number
+ * @param formattedNumber The formatted phone number
+ * @return The phone number info object
+ */
+ public ContactInfo lookupNumber(Context context,
+ String normalizedNumber, String formattedNumber) throws IOException {
+ YellowPagesApi ypa = new YellowPagesApi(context, normalizedNumber);
+ YellowPagesApi.ContactInfo info = ypa.getContactInfo();
+
+ if (info.name == null) {
+ return null;
+ }
+
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.REVERSE_LOOKUP,
+ normalizedNumber, formattedNumber);
+
+ builder.setName(ContactBuilder.Name.createDisplayName(info.name));
+ builder.addPhoneNumber(ContactBuilder.PhoneNumber.createMainNumber(info.formattedNumber));
+ builder.addWebsite(ContactBuilder.WebsiteUrl.createProfile(info.website));
+
+ if (info.address != null) {
+ ContactBuilder.Address a = new ContactBuilder.Address();
+ a.formattedAddress = info.address;
+ a.type = StructuredPostal.TYPE_WORK;
+ builder.addAddress(a);
+ }
+
+ if (info.photoUrl != null) {
+ builder.setPhotoUri(info.photoUrl);
+ } else {
+ builder.setPhotoUri(ContactBuilder.PHOTO_URI_BUSINESS);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/java/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java b/java/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java
new file mode 100644
index 0000000..eeedb12
--- /dev/null
+++ b/java/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup.zabasearch;
+
+import android.text.TextUtils;
+
+import com.android.dialer.lookup.LookupUtils;
+
+import java.io.IOException;
+
+public class ZabaSearchApi {
+ private static final String TAG = ZabaSearchApi.class.getSimpleName();
+
+ private static final String LOOKUP_URL = "https://www.zabasearch.com/phone/";
+
+ private String mNumber = null;
+ public String mOutput = null;
+ private ContactInfo mInfo = null;
+
+ public ZabaSearchApi(String number) {
+ mNumber = number;
+ }
+
+ private void fetchPage() throws IOException {
+ mOutput = LookupUtils.httpGet(LOOKUP_URL + mNumber, null);
+ }
+
+ private void buildContactInfo() {
+ // Name
+ String name = LookupUtils.firstRegexResult(mOutput,
+ "itemprop=\"?name\"?>([^<]+)<", true);
+ // Formatted phone number
+ String phoneNumber = LookupUtils.firstRegexResult(mOutput,
+ "itemprop=\"?telephone\"?>([^<]+)<", true);
+ // Address
+ String addressStreet = LookupUtils.firstRegexResult(mOutput,
+ "itemprop=\"?streetAddress\"?>([^<]+?)(&nbsp;)*<", true);
+ String addressCity = LookupUtils.firstRegexResult(mOutput,
+ "itemprop=\"?addressLocality\"?>([^<]+)<", true);
+ String addressState = LookupUtils.firstRegexResult(mOutput,
+ "itemprop=\"?addressRegion\"?>([^<]+)<", true);
+ String addressZip = LookupUtils.firstRegexResult(mOutput,
+ "itemprop=\"?postalCode\"?>([^<]+)<", true);
+
+ StringBuilder sb = new StringBuilder();
+
+ if (!TextUtils.isEmpty(addressStreet)) {
+ sb.append(addressStreet);
+ }
+ if (!TextUtils.isEmpty(addressCity)) {
+ sb.append(", ");
+ sb.append(addressCity);
+ }
+ if (!TextUtils.isEmpty(addressState)) {
+ sb.append(", ");
+ sb.append(addressState);
+ }
+ if (!TextUtils.isEmpty(addressZip)) {
+ sb.append(", ");
+ sb.append(addressZip);
+ }
+
+ String address = sb.toString();
+ if (address.length() == 0) {
+ address = null;
+ }
+
+ ContactInfo info = new ContactInfo();
+ info.name = name;
+ info.address = address;
+ info.formattedNumber = mNumber;
+ info.website = LOOKUP_URL + info.formattedNumber;
+ mInfo = info;
+ }
+
+ public ContactInfo getContactInfo() throws IOException {
+ if (mInfo == null) {
+ fetchPage();
+
+ buildContactInfo();
+ }
+
+ return mInfo;
+ }
+
+ public static class ContactInfo {
+ String name;
+ String address;
+ String formattedNumber;
+ String website;
+ }
+}
diff --git a/java/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java b/java/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java
new file mode 100644
index 0000000..2b53628
--- /dev/null
+++ b/java/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 Xiao-Long Chen <chillermillerlong@hotmail.com>
+ *
+ * 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.lookup.zabasearch;
+
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ReverseLookup;
+
+import android.content.Context;
+
+import java.io.IOException;
+
+public class ZabaSearchReverseLookup extends ReverseLookup {
+ private static final String TAG =
+ ZabaSearchReverseLookup.class.getSimpleName();
+
+ public ZabaSearchReverseLookup(Context context) {
+ }
+
+ /**
+ * Perform phone number lookup.
+ *
+ * @param context The application context
+ * @param normalizedNumber The normalized phone number
+ * @param formattedNumber The formatted phone number
+ * @return The phone number info object
+ */
+ public ContactInfo lookupNumber(Context context,
+ String normalizedNumber, String formattedNumber) throws IOException {
+ ZabaSearchApi zsa = new ZabaSearchApi(normalizedNumber);
+ ZabaSearchApi.ContactInfo info = zsa.getContactInfo();
+ if (info.name == null) {
+ return null;
+ }
+
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.REVERSE_LOOKUP,
+ normalizedNumber, formattedNumber);
+
+ builder.setName(ContactBuilder.Name.createDisplayName(info.name));
+ builder.addPhoneNumber(ContactBuilder.PhoneNumber.createMainNumber(info.formattedNumber));
+ builder.addWebsite(ContactBuilder.WebsiteUrl.createProfile(info.website));
+ if (info.address != null) {
+ builder.addAddress(ContactBuilder.Address.createFormattedHome(info.address));
+ }
+
+ return builder.build();
+ }
+}
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
index 4fa3147..e91b6b1 100644
--- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
+++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
@@ -42,6 +42,7 @@ import com.android.contacts.common.util.UriUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.logging.ContactSource;
+import com.android.dialer.lookup.LookupCache;
import com.android.dialer.oem.CequintCallerIdManager;
import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
@@ -433,6 +434,8 @@ public class ContactInfoHelper {
// Contact found in the extended directory specified by directoryId
info.sourceType = ContactSource.Type.SOURCE_TYPE_EXTENDED;
}
+ } else if (LookupCache.hasCachedContact(mContext, number)) {
+ info = LookupCache.getCachedContact(mContext, number);
} else if (mCachedNumberLookupService != null) {
CachedContactInfo cacheInfo =
mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number);