summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk7
-rw-r--r--AndroidManifest.xml9
-rw-r--r--assets/contacts_extensions.properties1
-rw-r--r--proguard.flags3
-rw-r--r--res/color/setting_primary_color.xml22
-rw-r--r--res/color/setting_secondary_color.xml22
-rw-r--r--res/drawable-hdpi/ic_places_picture_180_holo_light.pngbin0 -> 698 bytes
-rw-r--r--res/drawable-hdpi/ic_places_picture_holo_light.pngbin0 -> 424 bytes
-rw-r--r--res/drawable-xhdpi/ic_places_picture_180_holo_light.pngbin0 -> 1140 bytes
-rw-r--r--res/drawable-xhdpi/ic_places_picture_holo_light.pngbin0 -> 632 bytes
-rw-r--r--res/drawable-xxhdpi/ic_places_picture_180_holo_light.pngbin0 -> 1171 bytes
-rw-r--r--res/drawable-xxhdpi/ic_places_picture_holo_light.pngbin0 -> 636 bytes
-rw-r--r--res/values/cm_arrays.xml59
-rw-r--r--res/values/cm_strings.xml40
-rw-r--r--res/values/colors.xml4
-rw-r--r--res/xml/lookup_settings.xml67
-rw-r--r--src/com/android/dialer/calllog/ClearCallLogDialog.java2
-rw-r--r--src/com/android/dialer/calllog/ContactInfoHelper.java3
-rw-r--r--src/com/android/dialer/database/DialerDatabaseHelper.java7
-rw-r--r--src/com/android/dialer/list/RegularSearchFragment.java7
-rw-r--r--src/com/android/dialer/list/RegularSearchListAdapter.java14
-rw-r--r--src/com/android/dialer/lookup/ContactBuilder.java402
-rw-r--r--src/com/android/dialer/lookup/ExtendedLookupDirectories.java74
-rw-r--r--src/com/android/dialer/lookup/ForwardLookup.java62
-rw-r--r--src/com/android/dialer/lookup/LookupCache.java303
-rw-r--r--src/com/android/dialer/lookup/LookupProvider.java460
-rw-r--r--src/com/android/dialer/lookup/LookupSettings.java110
-rw-r--r--src/com/android/dialer/lookup/LookupUtils.java84
-rw-r--r--src/com/android/dialer/lookup/PeopleLookup.java55
-rw-r--r--src/com/android/dialer/lookup/ReverseLookup.java117
-rw-r--r--src/com/android/dialer/lookup/ReverseLookupService.java203
-rw-r--r--src/com/android/dialer/lookup/cyngn/CyngnChineseReverseLookup.java99
-rw-r--r--src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java86
-rw-r--r--src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java69
-rw-r--r--src/com/android/dialer/lookup/gebeld/GebeldApi.java96
-rw-r--r--src/com/android/dialer/lookup/gebeld/GebeldReverseLookup.java69
-rw-r--r--src/com/android/dialer/lookup/google/GoogleForwardLookup.java305
-rw-r--r--src/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java111
-rw-r--r--src/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java190
-rw-r--r--src/com/android/dialer/lookup/whitepages/WhitePagesApi.java353
-rw-r--r--src/com/android/dialer/lookup/whitepages/WhitePagesPeopleLookup.java78
-rw-r--r--src/com/android/dialer/lookup/whitepages/WhitePagesReverseLookup.java62
-rw-r--r--src/com/android/dialer/lookup/yellowpages/YellowPagesApi.java222
-rw-r--r--src/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java137
-rw-r--r--src/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java108
-rw-r--r--src/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java63
-rw-r--r--src/com/android/dialer/settings/DialerSettingsActivity.java6
-rw-r--r--src/com/android/dialer/settings/LookupSettingsFragment.java166
48 files changed, 4347 insertions, 10 deletions
diff --git a/Android.mk b/Android.mk
index a9481d5..d667947 100644
--- a/Android.mk
+++ b/Android.mk
@@ -20,6 +20,7 @@ res_dirs := res \
LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs)) \
frameworks/support/v7/cardview/res frameworks/support/v7/recyclerview/res
+LOCAL_ASSET_DIR += $(LOCAL_PATH)/assets
LOCAL_AAPT_FLAGS := \
--auto-add-overlay \
@@ -30,7 +31,8 @@ LOCAL_AAPT_FLAGS := \
--extra-packages com.android.phone.common
LOCAL_JAVA_LIBRARIES := telephony-common \
- ims-common
+ ims-common \
+ org.apache.http.legacy
LOCAL_STATIC_JAVA_LIBRARIES := \
android-common \
@@ -41,7 +43,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
com.android.services.telephony.common \
com.android.vcard \
guava \
- libphonenumber
+ libphonenumber \
+ org.cyanogenmod.platform.sdk
LOCAL_PACKAGE_NAME := Dialer
LOCAL_CERTIFICATE := shared
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4055b44..06182b1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -60,6 +60,7 @@
start requests, even if they happen immediately after the user
presses home. -->
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
+ <uses-permission android:name="cyanogenmod.permission.WRITE_SETTINGS" />
<application
android:name="DialerApplication"
@@ -73,6 +74,8 @@
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIBXgtCEKQ6W0PXVnW-ZVia2KmlV2AxsTw3GjAeQ" />
+ <uses-library android:name="org.apache.http.legacy" />
+
<!-- The entrance point for Phone UI.
stateAlwaysHidden is set to suppress keyboard show up on
dialpad screen. -->
@@ -306,5 +309,11 @@
<action android:name="android.telecom.InCallService"/>
</intent-filter>
</service>
+
+ <provider android:name="com.android.dialer.lookup.LookupProvider"
+ android:authorities="com.android.dialer.provider"
+ android:exported="false"
+ android:multiprocess="false" />
+
</application>
</manifest>
diff --git a/assets/contacts_extensions.properties b/assets/contacts_extensions.properties
new file mode 100644
index 0000000..535253f
--- /dev/null
+++ b/assets/contacts_extensions.properties
@@ -0,0 +1 @@
+extendedPhoneDirectories=com.android.dialer.lookup.ExtendedLookupDirectories
diff --git a/proguard.flags b/proguard.flags
index 38d4050..2bf5215 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -12,4 +12,7 @@
@com.android.dialer.NeededForReflection *;
}
+# Keep ExtendedLookupDirectories for assets/contacts_extensions.properties
+-keep class com.android.dialer.lookup.ExtendedLookupDirectories { *; }
+
-verbose
diff --git a/res/color/setting_primary_color.xml b/res/color/setting_primary_color.xml
new file mode 100644
index 0000000..6d083e2
--- /dev/null
+++ b/res/color/setting_primary_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="0.26"
+ android:color="@color/dialtacts_primary_text_color"/>
+ <item android:color="@color/dialtacts_primary_text_color"/>
+</selector>
diff --git a/res/color/setting_secondary_color.xml b/res/color/setting_secondary_color.xml
new file mode 100644
index 0000000..9ebbc4e
--- /dev/null
+++ b/res/color/setting_secondary_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="0.26"
+ android:color="@color/dialtacts_secondary_text_color"/>
+ <item android:color="@color/dialtacts_secondary_text_color"/>
+</selector>
diff --git a/res/drawable-hdpi/ic_places_picture_180_holo_light.png b/res/drawable-hdpi/ic_places_picture_180_holo_light.png
new file mode 100644
index 0000000..f0bbe73
--- /dev/null
+++ b/res/drawable-hdpi/ic_places_picture_180_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_places_picture_holo_light.png b/res/drawable-hdpi/ic_places_picture_holo_light.png
new file mode 100644
index 0000000..f70e8e7
--- /dev/null
+++ b/res/drawable-hdpi/ic_places_picture_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_places_picture_180_holo_light.png b/res/drawable-xhdpi/ic_places_picture_180_holo_light.png
new file mode 100644
index 0000000..6409ab1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_places_picture_180_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_places_picture_holo_light.png b/res/drawable-xhdpi/ic_places_picture_holo_light.png
new file mode 100644
index 0000000..7c92a60
--- /dev/null
+++ b/res/drawable-xhdpi/ic_places_picture_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_places_picture_180_holo_light.png b/res/drawable-xxhdpi/ic_places_picture_180_holo_light.png
new file mode 100644
index 0000000..97b9822
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_places_picture_180_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_places_picture_holo_light.png b/res/drawable-xxhdpi/ic_places_picture_holo_light.png
new file mode 100644
index 0000000..43029bd
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_places_picture_holo_light.png
Binary files differ
diff --git a/res/values/cm_arrays.xml b/res/values/cm_arrays.xml
new file mode 100644
index 0000000..c620600
--- /dev/null
+++ b/res/values/cm_arrays.xml
@@ -0,0 +1,59 @@
+<?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>WhitePages</item>
+ </string-array>
+
+ <string-array name="people_lookup_provider_names" translatable="false">
+ <item>WhitePages (US)</item>
+ </string-array>
+
+ <string-array name="reverse_lookup_providers" translatable="false">
+ <item>OpenCnam</item>
+ <item>DasTelefonbuch</item>
+ <item>Gebeld</item>
+ <item>WhitePages</item>
+ <item>WhitePages_CA</item>
+ <item>YellowPages</item>
+ <item>YellowPages_CA</item>
+ <item>ZabaSearch</item>
+ </string-array>
+
+ <string-array name="reverse_lookup_provider_names" translatable="false">
+ <item>OpenCnam (US)</item>
+ <item>Das Telefonbuch (DE)</item>
+ <item>Gebeld (NL)</item>
+ <item>WhitePages (US)</item>
+ <item>WhitePages (CA)</item>
+ <item>YellowPages (US)</item>
+ <item>YellowPages (CA)</item>
+ <item>ZabaSearch (US)</item>
+ </string-array>
+
+</resources>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
new file mode 100644
index 0000000..445c146
--- /dev/null
+++ b/res/values/cm_strings.xml
@@ -0,0 +1,40 @@
+<?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="lookup_settings_description">Lookup of unknown phone numbers</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>
+
+ <!-- Chinese Reverse Lookup Provider -->
+ <string name="cyngn_reverse_lookup_provider_package" translatable="false">com.cyngn.chineselocationlookup</string>
+ <string name="cyngn_reverse_lookup_provider_name">Cyngn Chinese (CN)</string>
+ <string name="cyngn_reverse_lookup_provider_value" translatable="false">CyngnChinese</string>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8ce3c17..f72ad3b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -22,10 +22,6 @@
<color name="dialer_red_highlight_color">#ff1744</color>
<color name="dialer_green_highlight_color">#00c853</color>
- <!-- Color for the setting text. -->
- <color name="setting_primary_color">@color/dialtacts_primary_text_color</color>
- <!-- Color for the setting description text. -->
- <color name="setting_secondary_color">@color/dialtacts_secondary_text_color</color>
<color name="setting_disabled_color">#aaaaaa</color>
<color name="setting_background_color">#ffffff</color>
<color name="setting_button_color">#eee</color>
diff --git a/res/xml/lookup_settings.xml b/res/xml/lookup_settings.xml
new file mode 100644
index 0000000..14d2de2
--- /dev/null
+++ b/res/xml/lookup_settings.xml
@@ -0,0 +1,67 @@
+<?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:dependency="enable_reverse_lookup"
+ android:summary="%s"
+ android:persistent="false" />
+
+</PreferenceScreen>
+
diff --git a/src/com/android/dialer/calllog/ClearCallLogDialog.java b/src/com/android/dialer/calllog/ClearCallLogDialog.java
index bef5010..ec28aec 100644
--- a/src/com/android/dialer/calllog/ClearCallLogDialog.java
+++ b/src/com/android/dialer/calllog/ClearCallLogDialog.java
@@ -31,6 +31,7 @@ import android.os.Bundle;
import android.provider.CallLog.Calls;
import com.android.dialer.R;
+import com.android.dialer.lookup.LookupCache;
import com.android.dialer.service.CachedNumberLookupService;
import com.android.dialerbind.ObjectFactory;
@@ -65,6 +66,7 @@ public class ClearCallLogDialog extends DialogFragment {
if (mCachedNumberLookupService != null) {
mCachedNumberLookupService.clearAllCacheEntries(context);
}
+ LookupCache.deleteCachedContacts(context);
return null;
}
@Override
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index 2e07a03..4e2fe5c 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -33,6 +33,7 @@ import com.android.contacts.common.util.Constants;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.PhoneNumberHelper;
import com.android.contacts.common.util.UriUtils;
+import com.android.dialer.lookup.LookupCache;
import com.android.dialer.service.CachedNumberLookupService;
import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
import com.android.dialer.util.TelecomUtil;
@@ -252,6 +253,8 @@ public class ContactInfoHelper {
ContactInfo info = lookupContactFromUri(uri);
if (info != null && info != ContactInfo.EMPTY) {
info.formattedNumber = formatPhoneNumber(number, null, countryIso);
+ } else if (LookupCache.hasCachedContact(mContext, number)) {
+ info = LookupCache.getCachedContact(mContext, number);
} else if (mCachedNumberLookupService != null) {
CachedContactInfo cacheInfo =
mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number);
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index eec24f5..60179f6 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -73,7 +73,7 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper {
* 0-98 KitKat
* </pre>
*/
- public static final int DATABASE_VERSION = 4;
+ public static final int DATABASE_VERSION = 70004;
public static final String DATABASE_NAME = "dialer.db";
/**
@@ -409,7 +409,10 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper {
Log.e(TAG, "Malformed database version..recreating database");
}
- if (oldVersion < 4) {
+ int base = 70000;
+ db.execSQL("DROP TABLE IF EXISTS " + "cached_number_contacts");
+ if (oldVersion <= (DATABASE_VERSION - base)
+ || (oldVersion >= base && oldVersion < DATABASE_VERSION)) {
setupTables(db);
return;
}
diff --git a/src/com/android/dialer/list/RegularSearchFragment.java b/src/com/android/dialer/list/RegularSearchFragment.java
index b7e26d6..9027974 100644
--- a/src/com/android/dialer/list/RegularSearchFragment.java
+++ b/src/com/android/dialer/list/RegularSearchFragment.java
@@ -30,6 +30,7 @@ import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialerbind.ObjectFactory;
import com.android.dialer.R;
+import com.android.dialer.lookup.LookupCache;
import com.android.dialer.service.CachedNumberLookupService;
import com.android.dialer.widget.EmptyContentView;
import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
@@ -74,12 +75,14 @@ public class RegularSearchFragment extends SearchFragment
@Override
protected void cacheContactInfo(int position) {
- if (mCachedNumberLookupService != null) {
- final RegularSearchListAdapter adapter =
+ final RegularSearchListAdapter adapter =
(RegularSearchListAdapter) getAdapter();
+ if (mCachedNumberLookupService != null) {
mCachedNumberLookupService.addContact(getContext(),
adapter.getContactInfo(mCachedNumberLookupService, position));
}
+ LookupCache.cacheContact(getActivity(),
+ adapter.getLookupContactInfo(position));
}
@Override
diff --git a/src/com/android/dialer/list/RegularSearchListAdapter.java b/src/com/android/dialer/list/RegularSearchListAdapter.java
index 2be8a1d..944fec6 100644
--- a/src/com/android/dialer/list/RegularSearchListAdapter.java
+++ b/src/com/android/dialer/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();
diff --git a/src/com/android/dialer/lookup/ContactBuilder.java b/src/com/android/dialer/lookup/ContactBuilder.java
new file mode 100644
index 0000000..069045d
--- /dev/null
+++ b/src/com/android/dialer/lookup/ContactBuilder.java
@@ -0,0 +1,402 @@
+/*
+ * 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.contacts.common.util.Constants;
+import com.android.dialer.R;
+import com.android.dialer.calllog.ContactInfo;
+
+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.util.Log;
+
+import java.util.ArrayList;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+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 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 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 Website[] getWebsites() {
+ return mWebsites.toArray(new Website[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) {
+ 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 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;
+ if (mDirectoryType == FORWARD_LOOKUP
+ || mDirectoryType == PEOPLE_LOOKUP) {
+ directoryId = ContactsContract.Directory.DEFAULT;
+ } else if (mDirectoryType == REVERSE_LOOKUP) {
+ directoryId = Long.MAX_VALUE;
+ }
+
+ 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 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 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 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 String toString() {
+ return "url: " + url + "; " +
+ "type: " + type + "; " +
+ "label: " + label;
+ }
+ }
+}
diff --git a/src/com/android/dialer/lookup/ExtendedLookupDirectories.java b/src/com/android/dialer/lookup/ExtendedLookupDirectories.java
new file mode 100644
index 0000000..4a50560
--- /dev/null
+++ b/src/com/android/dialer/lookup/ExtendedLookupDirectories.java
@@ -0,0 +1,74 @@
+/*
+ * 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.R;
+
+import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager;
+import com.android.contacts.common.list.DirectoryPartition;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExtendedLookupDirectories
+ implements ExtendedPhoneDirectoriesManager {
+ public static final String TAG =
+ ExtendedLookupDirectories.class.getSimpleName();
+
+ /**
+ * Return a list of extended directories to add. May return null if no directories are to be
+ * added.
+ */
+ @Override
+ public 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(LookupProvider.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(LookupProvider.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;
+ }
+}
diff --git a/src/com/android/dialer/lookup/ForwardLookup.java b/src/com/android/dialer/lookup/ForwardLookup.java
new file mode 100644
index 0000000..d67d000
--- /dev/null
+++ b/src/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.calllog.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/src/com/android/dialer/lookup/LookupCache.java b/src/com/android/dialer/lookup/LookupCache.java
new file mode 100644
index 0000000..e0dc66c
--- /dev/null
+++ b/src/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.calllog.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 libcore.io.IoUtils;
+
+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 {
+ IoUtils.closeQuietly(writer);
+ IoUtils.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 {
+ IoUtils.closeQuietly(reader);
+ IoUtils.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 {
+ IoUtils.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/src/com/android/dialer/lookup/LookupProvider.java b/src/com/android/dialer/lookup/LookupProvider.java
new file mode 100644
index 0000000..4906d32
--- /dev/null
+++ b/src/com/android/dialer/lookup/LookupProvider.java
@@ -0,0 +1,460 @@
+/*
+ * 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.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.R;
+
+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 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.LinkedList;
+
+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 final String AUTHORITY = "com.android.dialer.provider";
+ 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);
+
+ final int match = sURIMatcher.match(uri);
+
+ switch (match) {
+ case NEARBY:
+ case PEOPLE:
+ Context context = getContext();
+ if (!isLocationEnabled()) {
+ Log.v(TAG, "Location settings is disabled, ignoring query.");
+ return null;
+ }
+
+ final Location lastLocation = getLastLocation();
+ if (lastLocation == null) {
+ Log.v(TAG, "No location available, ignoring query.");
+ return null;
+ }
+
+ 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 int finalMaxResults = maxResults;
+
+ return execute(new Callable<Cursor>() {
+ @Override
+ public Cursor call() {
+ return handleFilter(match, projection, filter,
+ finalMaxResults, lastLocation);
+ }
+ }, "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/src/com/android/dialer/lookup/LookupSettings.java b/src/com/android/dialer/lookup/LookupSettings.java
new file mode 100644
index 0000000..8e0e18c
--- /dev/null
+++ b/src/com/android/dialer/lookup/LookupSettings.java
@@ -0,0 +1,110 @@
+/*
+ * 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 cyanogenmod.providers.CMSettings;
+
+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_WHITEPAGES = "WhitePages";
+ public static final String PLP_DEFAULT = PLP_WHITEPAGES;
+
+ /** Reverse lookup providers */
+ public static final String RLP_OPENCNAM = "OpenCnam";
+ public static final String RLP_WHITEPAGES = "WhitePages";
+ public static final String RLP_WHITEPAGES_CA = "WhitePages_CA";
+ 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_GEBELD = "Gebeld";
+ public static final String RLP_DEFAULT = RLP_OPENCNAM;
+
+ private LookupSettings() {
+ }
+
+ public static boolean isForwardLookupEnabled(Context context) {
+ return CMSettings.System.getInt(context.getContentResolver(),
+ CMSettings.System.ENABLE_FORWARD_LOOKUP, 1) != 0;
+ }
+
+ public static boolean isPeopleLookupEnabled(Context context) {
+ return CMSettings.System.getInt(context.getContentResolver(),
+ CMSettings.System.ENABLE_PEOPLE_LOOKUP, 1) != 0;
+ }
+
+ public static boolean isReverseLookupEnabled(Context context) {
+ return CMSettings.System.getInt(context.getContentResolver(),
+ CMSettings.System.ENABLE_REVERSE_LOOKUP, 1) != 0;
+ }
+
+ public static String getForwardLookupProvider(Context context) {
+ String provider = getLookupProvider(context,
+ CMSettings.System.FORWARD_LOOKUP_PROVIDER, FLP_DEFAULT);
+
+ return provider;
+ }
+
+ public static String getPeopleLookupProvider(Context context) {
+ String provider = getLookupProvider(context,
+ CMSettings.System.PEOPLE_LOOKUP_PROVIDER, PLP_DEFAULT);
+
+ return provider;
+ }
+
+ public static String getReverseLookupProvider(Context context) {
+ String provider = getLookupProvider(context,
+ CMSettings.System.REVERSE_LOOKUP_PROVIDER, RLP_DEFAULT);
+
+ if ("Google".equals(provider)) {
+ CMSettings.System.putString(context.getContentResolver(),
+ CMSettings.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 = CMSettings.System.getString(cr, key);
+
+ if (provider == null) {
+ CMSettings.System.putString(cr, key, defaultValue);
+ return defaultValue;
+ }
+
+ return provider;
+ }
+}
diff --git a/src/com/android/dialer/lookup/LookupUtils.java b/src/com/android/dialer/lookup/LookupUtils.java
new file mode 100644
index 0000000..c0b84dc
--- /dev/null
+++ b/src/com/android/dialer/lookup/LookupUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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 org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+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:26.0) Gecko/20100101 Firefox/26.0";
+
+ public static String httpGet(HttpGet request) throws IOException {
+ HttpClient client = new DefaultHttpClient();
+
+ request.setHeader("User-Agent", USER_AGENT);
+
+ HttpResponse response = client.execute(request);
+ int status = response.getStatusLine().getStatusCode();
+
+ // Android's org.apache.http doesn't have the RedirectStrategy class
+ if (status == HttpStatus.SC_MOVED_PERMANENTLY
+ || status == HttpStatus.SC_MOVED_TEMPORARILY) {
+ Header[] headers = response.getHeaders("Location");
+
+ if (headers != null && headers.length != 0) {
+ HttpGet newGet = new HttpGet(headers[headers.length - 1].getValue());
+ for (Header header : request.getAllHeaders()) {
+ newGet.addHeader(header);
+ }
+ return httpGet(newGet);
+ } else {
+ throw new IOException("Empty redirection header");
+ }
+ }
+
+ if (status != HttpStatus.SC_OK) {
+ throw new IOException("HTTP failure (status " + status + ")");
+ }
+
+ return EntityUtils.toString(response.getEntity());
+ }
+
+ 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/src/com/android/dialer/lookup/PeopleLookup.java b/src/com/android/dialer/lookup/PeopleLookup.java
new file mode 100644
index 0000000..08c3d7d
--- /dev/null
+++ b/src/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.calllog.ContactInfo;
+import com.android.dialer.lookup.whitepages.WhitePagesPeopleLookup;
+
+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_WHITEPAGES)) {
+ INSTANCE = new WhitePagesPeopleLookup(context);
+ }
+ }
+
+ return INSTANCE;
+ }
+
+ private static boolean isInstance(String provider) {
+ if (provider.equals(LookupSettings.PLP_WHITEPAGES)
+ && INSTANCE instanceof WhitePagesPeopleLookup) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public abstract ContactInfo[] lookup(Context context,
+ String filter);
+}
diff --git a/src/com/android/dialer/lookup/ReverseLookup.java b/src/com/android/dialer/lookup/ReverseLookup.java
new file mode 100644
index 0000000..32da8ae
--- /dev/null
+++ b/src/com/android/dialer/lookup/ReverseLookup.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;
+
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.lookup.cyngn.CyngnChineseReverseLookup;
+import com.android.dialer.lookup.dastelefonbuch.TelefonbuchReverseLookup;
+import com.android.dialer.lookup.gebeld.GebeldReverseLookup;
+import com.android.dialer.lookup.opencnam.OpenCnamReverseLookup;
+import com.android.dialer.lookup.whitepages.WhitePagesReverseLookup;
+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_WHITEPAGES)
+ || provider.equals(LookupSettings.RLP_WHITEPAGES_CA)) {
+ INSTANCE = new WhitePagesReverseLookup(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_CYNGN_CHINESE)) {
+ INSTANCE = new CyngnChineseReverseLookup(context);
+ } else if (provider.equals(LookupSettings.RLP_DASTELEFONBUCH)) {
+ INSTANCE = new TelefonbuchReverseLookup(context);
+ } else if (provider.equals(LookupSettings.RLP_GEBELD)) {
+ INSTANCE = new GebeldReverseLookup(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_WHITEPAGES)
+ || provider.equals(LookupSettings.RLP_WHITEPAGES_CA))
+ && INSTANCE instanceof WhitePagesReverseLookup) {
+ 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_CYNGN_CHINESE)
+ && INSTANCE instanceof CyngnChineseReverseLookup) {
+ return true;
+ } else if (provider.equals(LookupSettings.RLP_DASTELEFONBUCH)
+ && INSTANCE instanceof TelefonbuchReverseLookup) {
+ return true;
+ } else if (provider.equals(LookupSettings.RLP_GEBELD)
+ && INSTANCE instanceof GebeldReverseLookup) {
+ 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/src/com/android/dialer/lookup/ReverseLookupService.java b/src/com/android/dialer/lookup/ReverseLookupService.java
new file mode 100644
index 0000000..25c3f4a
--- /dev/null
+++ b/src/com/android/dialer/lookup/ReverseLookupService.java
@@ -0,0 +1,203 @@
+/*
+ * 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.contacts.common.GeoUtil;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.incallui.service.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;
+ }
+
+ // Can't do reverse lookup without a number
+ if (phoneNumber == null) {
+ return;
+ }
+
+ LookupRequest request = new LookupRequest();
+ String countryIso = mTelephonyManager.getSimCountryIso().toUpperCase();
+ request.normalizedNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, countryIso);
+ 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;
+ }
+ }
+}
diff --git a/src/com/android/dialer/lookup/cyngn/CyngnChineseReverseLookup.java b/src/com/android/dialer/lookup/cyngn/CyngnChineseReverseLookup.java
new file mode 100644
index 0000000..355208a
--- /dev/null
+++ b/src/com/android/dialer/lookup/cyngn/CyngnChineseReverseLookup.java
@@ -0,0 +1,99 @@
+/*
+ * 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.cyngn;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.util.Log;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ReverseLookup;
+
+public class CyngnChineseReverseLookup extends ReverseLookup {
+
+ private static final String TAG = CyngnChineseReverseLookup.class.getSimpleName();
+
+ private static final int COMMON_CHINESE_PHONE_NUMBER_LENGTH = 11;
+ private static final int COMMON_CHINESE_PHONE_NUMBER_AREANO_START = 2;
+ private static final int COMMON_CHINESE_PHONE_NUMBER_AREANO_END = 5;
+
+ private static final boolean DEBUG = false;
+ private static final Uri PROVIDER_URI =
+ Uri.parse("content://com.cyngn.chineselocationlookup.provider");
+
+ public CyngnChineseReverseLookup(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) {
+ String displayName = queryProvider(context, normalizedNumber);
+ if (displayName == null) {
+ return null;
+ }
+
+ if (DEBUG) Log.d(TAG, "Reverse lookup returned name: " + displayName);
+
+ 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 queryProvider(Context context, String normalizedNumber) {
+ if (normalizedNumber.length() < COMMON_CHINESE_PHONE_NUMBER_LENGTH) {
+ return null;
+ }
+
+ //trim carrier code, and get area prefix
+ String areaPrefix = normalizedNumber.substring(COMMON_CHINESE_PHONE_NUMBER_AREANO_START,
+ COMMON_CHINESE_PHONE_NUMBER_AREANO_END);
+
+ ContentResolver resolver = context.getContentResolver();
+ Cursor cursor = context.getContentResolver().query(PROVIDER_URI,
+ null, null, new String[] { areaPrefix }, null);
+ if (cursor == null) {
+ return null;
+ }
+
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getString(2);
+ }
+ } finally {
+ cursor.close();
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java b/src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java
new file mode 100644
index 0000000..2d9ed3e
--- /dev/null
+++ b/src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchApi.java
@@ -0,0 +1,86 @@
+/*
+ * 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 org.apache.http.client.methods.HttpGet;
+
+import java.io.IOException;
+
+public class TelefonbuchApi {
+ private static final String TAG = TelefonbuchApi.class.getSimpleName();
+
+ private static final String REVERSE_LOOKUP_URL =
+ "http://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(new HttpGet(uri.toString())),
+ ": 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) {
+ result = result.replaceAll("</?span.*?>", "");
+ }
+ return LookupUtils.fromHtml(result);
+ }
+
+ public static class ContactInfo {
+ String name;
+ String address;
+ String formattedNumber;
+ String website;
+ }
+}
diff --git a/src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java b/src/com/android/dialer/lookup/dastelefonbuch/TelefonbuchReverseLookup.java
new file mode 100644
index 0000000..a96a772
--- /dev/null
+++ b/src/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.calllog.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/src/com/android/dialer/lookup/gebeld/GebeldApi.java b/src/com/android/dialer/lookup/gebeld/GebeldApi.java
new file mode 100644
index 0000000..effeb78
--- /dev/null
+++ b/src/com/android/dialer/lookup/gebeld/GebeldApi.java
@@ -0,0 +1,96 @@
+/*
+ * 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.gebeld;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.dialer.lookup.LookupUtils;
+
+import org.apache.http.client.methods.HttpGet;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GebeldApi {
+ private static final String TAG = GebeldApi.class.getSimpleName();
+
+ private static final String REVERSE_LOOKUP_URL =
+ "http://www.gebeld.nl/zoeken.asp?Page=4,%201" +
+ "&searchfield1=fullnumber&action=Zoeken";
+
+ private static String REGEX = "\n?\\s*(.*)<(?:(?:BR|br)\\s*/?)>";
+
+ private GebeldApi() {
+ }
+
+ public static ContactInfo reverseLookup(Context context, String number)
+ throws IOException {
+ String phoneNumber = number.replace("+31", "0");
+ Uri uri = Uri.parse(REVERSE_LOOKUP_URL)
+ .buildUpon()
+ .appendQueryParameter("queryfield1", phoneNumber)
+ .build();
+ // Cut out everything we're not interested in (scripts etc.) to
+ // speed up the subsequent matching.
+ String output = LookupUtils.firstRegexResult(
+ LookupUtils.httpGet(new HttpGet(uri.toString())),
+ "<div class=\"small-12 large-4 columns information\">(.*?)</div>", true);
+
+ String name = null;
+ String address = null;
+
+ if (output == null) {
+ return null;
+ } else {
+ Pattern pattern = Pattern.compile(REGEX, 0);
+ Matcher m = pattern.matcher(output);
+
+ if (m.find()) {
+ name = LookupUtils.fromHtml(m.group(1).trim());
+
+ if (m.find()) {
+ address = LookupUtils.fromHtml(m.group(1).trim());
+
+ if (m.find()) {
+ address += "\n" + LookupUtils.fromHtml(m.group(1).trim());
+ }
+ }
+ }
+ }
+
+ if (name == null) {
+ return null;
+ }
+
+ ContactInfo info = new ContactInfo();
+ info.name = name;
+ info.address = address;
+ info.formattedNumber = phoneNumber != null ? phoneNumber : number;
+ info.website = uri.toString();
+
+ return info;
+ }
+
+ public static class ContactInfo {
+ String name;
+ String address;
+ String formattedNumber;
+ String website;
+ }
+}
diff --git a/src/com/android/dialer/lookup/gebeld/GebeldReverseLookup.java b/src/com/android/dialer/lookup/gebeld/GebeldReverseLookup.java
new file mode 100644
index 0000000..9f1be2e
--- /dev/null
+++ b/src/com/android/dialer/lookup/gebeld/GebeldReverseLookup.java
@@ -0,0 +1,69 @@
+/*
+ * 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.gebeld;
+
+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.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ReverseLookup;
+
+import java.io.IOException;
+
+public class GebeldReverseLookup extends ReverseLookup {
+ private static final String TAG = GebeldReverseLookup.class.getSimpleName();
+
+ public GebeldReverseLookup(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("+31")) {
+ // Only handle Dutch numbers
+ return null;
+ }
+
+ GebeldApi.ContactInfo info = GebeldApi.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/src/com/android/dialer/lookup/google/GoogleForwardLookup.java b/src/com/android/dialer/lookup/google/GoogleForwardLookup.java
new file mode 100644
index 0000000..215cbfd
--- /dev/null
+++ b/src/com/android/dialer/lookup/google/GoogleForwardLookup.java
@@ -0,0 +1,305 @@
+/*
+ * 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.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ForwardLookup;
+
+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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+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());
+
+ String httpResponse = httpGetRequest(
+ builder.build().toString());
+
+ JSONArray results = new JSONArray(httpResponse);
+
+ 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);
+ }
+
+ /**
+ * Fetch a URL and return the response as a string encoded in either
+ * UTF-8 or the charset specified in the Content-Type header.
+ *
+ * @param url URL
+ * @return Response from server
+ */
+ private String httpGetRequest(String url) throws IOException {
+ HttpClient client = new DefaultHttpClient();
+ HttpGet request = new HttpGet(url.toString());
+
+ request.setHeader("User-Agent", mUserAgent);
+
+ HttpResponse response = client.execute(request);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ response.getEntity().writeTo(out);
+
+ String charset = getCharsetFromContentType(
+ response.getEntity().getContentType().getValue());
+
+ return new String(out.toByteArray(), charset);
+ }
+
+ /**
+ * Extract the content encoding from the HTTP 'Content-Type' header.
+ *
+ * @param contentType The 'Content-Type' header
+ * @return The charset or "UTF-8"
+ */
+ private static String getCharsetFromContentType(String contentType) {
+ String[] split = contentType.split(";");
+
+ for (int i = 0; i < split.length; i++) {
+ String trimmed = split[i].trim();
+ if (trimmed.startsWith("charset=")) {
+ return trimmed.substring(8);
+ }
+ }
+
+ return "UTF-8";
+ }
+
+ /**
+ * 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/src/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java b/src/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java
new file mode 100644
index 0000000..ad8507a
--- /dev/null
+++ b/src/com/android/dialer/lookup/opencnam/OpenCnamReverseLookup.java
@@ -0,0 +1,111 @@
+/*
+ * 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.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.LookupUtils;
+import com.android.dialer.lookup.ReverseLookup;
+
+import cyanogenmod.providers.CMSettings;
+
+import org.apache.http.client.methods.HttpGet;
+
+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 = CMSettings.System.getString(
+ context.getContentResolver(),
+ CMSettings.System.DIALER_OPENCNAM_ACCOUNT_SID);
+ String authToken = CMSettings.System.getString(
+ context.getContentResolver(),
+ CMSettings.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(new HttpGet(builder.build().toString()));
+ }
+}
diff --git a/src/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java b/src/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java
new file mode 100644
index 0000000..5bd5e72
--- /dev/null
+++ b/src/com/android/dialer/lookup/openstreetmap/OpenStreetMapForwardLookup.java
@@ -0,0 +1,190 @@
+/*
+ * 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.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ForwardLookup;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+
+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 =
+ "http://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 {
+ String httpResponse = httpPostRequest(request);
+
+ JSONObject results = new JSONObject(httpResponse);
+
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ private String httpPostRequest(String query) throws IOException {
+ HttpClient client = new DefaultHttpClient();
+ HttpPost post = new HttpPost(LOOKUP_URL);
+
+ post.setEntity(new StringEntity(query));
+
+ return EntityUtils.toString(client.execute(post).getEntity());
+ }
+}
diff --git a/src/com/android/dialer/lookup/whitepages/WhitePagesApi.java b/src/com/android/dialer/lookup/whitepages/WhitePagesApi.java
new file mode 100644
index 0000000..5b266bf
--- /dev/null
+++ b/src/com/android/dialer/lookup/whitepages/WhitePagesApi.java
@@ -0,0 +1,353 @@
+/*
+ * 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.whitepages;
+
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.dialer.lookup.LookupSettings;
+import com.android.dialer.lookup.LookupUtils;
+
+import org.apache.http.client.methods.HttpGet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class WhitePagesApi {
+ private static final String TAG = WhitePagesApi.class.getSimpleName();
+
+ public static final int UNITED_STATES = 0;
+ public static final int CANADA = 1;
+
+ private static final String NEARBY_URL_UNITED_STATES =
+ "http://www.whitepages.com/search/ReversePhone?full_phone=";
+ private static final String NEARBY_URL_CANADA =
+ "http://www.whitepages.ca/search/ReversePhone?full_phone=";
+
+ private static final String PEOPLE_URL_UNITED_STATES =
+ "http://whitepages.com/search/FindPerson";
+
+ private static final String USER_AGENT =
+ "Mozilla/5.0 (X11; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0";
+ private static final String COOKIE_REGEX = "distil_RID=([A-Za-z0-9\\-]+)";
+ private static final String COOKIE = "D_UID";
+
+ private static String mCookie;
+
+ private WhitePagesApi() {
+ }
+
+ public static ContactInfo[] peopleLookup(Context context, String name,
+ int maxResults) throws IOException {
+ String provider = LookupSettings.getPeopleLookupProvider(context);
+
+ if (LookupSettings.PLP_WHITEPAGES.equals(provider)) {
+ Uri.Builder builder = Uri.parse(PEOPLE_URL_UNITED_STATES)
+ .buildUpon();
+ builder.appendQueryParameter("who", name);
+ String lookupUrl = builder.build().toString();
+ String output = httpGet(lookupUrl);
+ return parseOutputUnitedStates(output, maxResults);
+ }
+ // no-op
+ return null;
+ }
+
+ private static ContactInfo[] parseOutputUnitedStates(String output,
+ int maxResults) throws IOException {
+ ArrayList<ContactInfo> people = new ArrayList<ContactInfo>();
+
+ Pattern regex = Pattern.compile(
+ "<li\\s[^>]+?http:\\/\\/schema\\.org\\/Person", Pattern.DOTALL);
+ Matcher m = regex.matcher(output);
+
+ while (m.find()) {
+ if (people.size() == maxResults) {
+ break;
+ }
+
+ // Find section of HTML with contact information
+ String section = extractXmlTag(output, m.start(), m.end(), "li");
+
+ // Skip entries with no phone number
+ if (section.contains("has-no-phone-icon")) {
+ continue;
+ }
+
+ String name = LookupUtils.fromHtml(extractXmlRegex(section,
+ "<span[^>]+?itemprop=\"name\">", "span"));
+
+ if (name == null) {
+ continue;
+ }
+
+ // Address
+ String addrCountry = LookupUtils.fromHtml(extractXmlRegex(section,
+ "<span[^>]+?itemprop=\"addressCountry\">", "span"));
+ String addrState = LookupUtils.fromHtml(extractXmlRegex(section,
+ "<span[^>]+?itemprop=\"addressRegion\">", "span"));
+ String addrCity = LookupUtils.fromHtml(extractXmlRegex(section,
+ "<span[^>]+?itemprop=\"addressLocality\">", "span"));
+
+ StringBuilder sb = new StringBuilder();
+
+ if (addrCity != null) {
+ sb.append(addrCity);
+ }
+ if (addrState != null) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(addrState);
+ }
+ if (addrCountry != null) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(addrCountry);
+ }
+
+ // Website
+ Pattern p = Pattern.compile("href=\"(.+?)\"");
+ Matcher m2 = p.matcher(section);
+ String website = null;
+ if (m2.find()) {
+ website = "http://www.whitepages.com" + m2.group(1);
+ }
+
+ // Phone number is on profile page, so skip if we can't get the
+ // website
+ if (website == null) {
+ continue;
+ }
+
+ String profile = httpGet(website);
+ String phoneNumber = LookupUtils.fromHtml(extractXmlRegex(profile,
+ "<li[^>]+?class=\"no-overflow tel\">", "li"));
+ String address = parseAddressUnitedStates(profile);
+
+ if (phoneNumber == null) {
+ Log.e(TAG, "Phone number is null. Either cookie is bad or regex is broken");
+ continue;
+ }
+
+ ContactInfo info = new ContactInfo();
+ info.name = name;
+ info.city = sb.toString();
+ info.address = address;
+ info.formattedNumber = phoneNumber;
+ info.website = website;
+
+ people.add(info);
+ }
+
+ return people.toArray(new ContactInfo[people.size()]);
+ }
+
+ private static String extractXmlRegex(String str, String regex, String tag) {
+ Pattern p = Pattern.compile(regex, Pattern.DOTALL);
+ Matcher m = p.matcher(str);
+ if (m.find()) {
+ return extractXmlTag(str, m.start(), m.end(), tag);
+ }
+ return null;
+ }
+
+ private static String extractXmlTag(String str, int realBegin, int begin,
+ String tag) {
+ int end = begin;
+ int tags = 1;
+ int maxLoop = 30;
+
+ while (tags > 0) {
+ end = str.indexOf(tag, end + 1);
+ if (end < 0 || maxLoop < 0) {
+ break;
+ }
+
+ if (str.charAt(end - 1) == '/'
+ && str.charAt(end + tag.length()) == '>') {
+ tags--;
+ } else if (str.charAt(end - 1) == '<') {
+ tags++;
+ }
+
+ maxLoop--;
+ }
+
+ int realEnd = str.indexOf(">", end) + 1;
+
+ if (tags != 0) {
+ Log.e(TAG, "Failed to extract tag <" + tag + "> from XML/HTML");
+ return null;
+ }
+
+ return str.substring(realBegin, realEnd);
+ }
+
+ public static ContactInfo reverseLookup(Context context, String number)
+ throws IOException {
+ String provider = LookupSettings.getReverseLookupProvider(context);
+
+ String lookupUrl = null;
+ if (LookupSettings.RLP_WHITEPAGES.equals(provider)) {
+ lookupUrl = NEARBY_URL_UNITED_STATES;
+ } else if (LookupSettings.RLP_WHITEPAGES_CA.equals(provider)) {
+ lookupUrl = NEARBY_URL_CANADA;
+ }
+ String newLookupUrl = lookupUrl + number;
+
+ String output = httpGet(newLookupUrl);
+
+ //
+
+ String name = null;
+ String phoneNumber = null;
+ String address = null;
+
+ if (LookupSettings.RLP_WHITEPAGES.equals(provider)) {
+ name = parseNameUnitedStates(output);
+ phoneNumber = parseNumberUnitedStates(output);
+ address = parseAddressUnitedStates(output);
+ } else if (LookupSettings.RLP_WHITEPAGES_CA.equals(provider)) {
+ name = parseNameCanada(output);
+ // Canada's WhitePages does not provide a formatted number
+ address = parseAddressCanada(output);
+ }
+
+ ContactInfo info = new ContactInfo();
+ info.name = name;
+ info.address = address;
+ info.formattedNumber = phoneNumber != null ? phoneNumber : number;
+ info.website = lookupUrl + info.formattedNumber;
+
+ return info;
+ }
+
+ private static String httpGet(String url) throws IOException {
+ HttpGet get = new HttpGet(url);
+
+ if (mCookie != null) {
+ get.setHeader("Cookie", COOKIE + "=" + mCookie);
+ }
+
+ String output = LookupUtils.httpGet(get);
+ // If we can find a new cookie, use it
+ Pattern p = Pattern.compile(COOKIE_REGEX, Pattern.DOTALL);
+ Matcher m = p.matcher(output);
+ if (m.find()) {
+ mCookie = m.group(1).trim();
+ Log.v(TAG, "Got new cookie");
+ }
+
+ // If we hit a page with a <meta> refresh and the error URL, reload. If
+ // this results in infinite recursion, then whatever. The thread is
+ // killed after 10 seconds.
+ p = Pattern.compile("<meta[^>]+http-equiv=\"refresh\"", Pattern.DOTALL);
+ m = p.matcher(output);
+ if (m.find() && output.contains("distil_r_captcha.html")) {
+ Log.w(TAG, "Got <meta> refresh. Reloading...");
+ return httpGet(url);
+ }
+
+ return output;
+ }
+
+ private static String parseNameUnitedStates(String output) {
+ String name = LookupUtils.firstRegexResult(output,
+ "<h2.*?>Send (.*?)&#39;s details to phone</h2>", true);
+
+ // Use summary if name doesn't exist
+ if (name == null) {
+ name = LookupUtils.firstRegexResult(output,
+ "<span\\s*class=\"subtitle.*?>\\s*\n?(.*?)\n?\\s*</span>", true);
+ }
+
+ if (name != null) {
+ name = name.replaceAll("&amp;", "&");
+ }
+
+ return name;
+ }
+
+ private static String parseNameCanada(String output) {
+ String name = LookupUtils.firstRegexResult(output,
+ "(<li\\s+class=\"listing_info\">.*?</li>)", true);
+ return LookupUtils.fromHtml(name);
+ }
+
+ private static String parseNumberUnitedStates(String output) {
+ return LookupUtils.firstRegexResult(output,
+ "Full Number:</span>([0-9\\-\\+\\(\\)]+)</li>", true);
+ }
+
+ private static String parseAddressUnitedStates(String output) {
+ String regexBase = "<span\\s+class=\"%s[^\"]+\"\\s*>([^<]*)</span>";
+
+ String addressPrimary = LookupUtils.firstRegexResult(output,
+ String.format(regexBase, "address-primary"), true);
+ String addressSecondary = LookupUtils.firstRegexResult(output,
+ String.format(regexBase, "address-secondary"), true);
+ String addressLocation = LookupUtils.firstRegexResult(output,
+ String.format(regexBase, "address-location"), true);
+
+ StringBuilder sb = new StringBuilder();
+
+ if (!TextUtils.isEmpty(addressPrimary)) {
+ sb.append(addressPrimary);
+ }
+ if (!TextUtils.isEmpty(addressSecondary)) {
+ sb.append(", ");
+ sb.append(addressSecondary);
+ }
+ if (!TextUtils.isEmpty(addressLocation)) {
+ sb.append(", ");
+ sb.append(addressLocation);
+ }
+
+ String address = sb.toString();
+ if (address.length() == 0) {
+ address = null;
+ }
+
+ return address;
+ }
+
+ private static String parseAddressCanada(String output) {
+ String address = LookupUtils.firstRegexResult(output,
+ "<ol class=\"result people_result\">.*?(<li\\s+class=\"col_location\">.*?</li>)" +
+ ".*?</ol>", true);
+
+ if (address != null) {
+ address = LookupUtils.fromHtml(address).replace("\n", ", ");
+ }
+
+ return address;
+ }
+
+ public static class ContactInfo {
+ String name;
+ String city;
+ String address;
+ String formattedNumber;
+ String website;
+ }
+}
diff --git a/src/com/android/dialer/lookup/whitepages/WhitePagesPeopleLookup.java b/src/com/android/dialer/lookup/whitepages/WhitePagesPeopleLookup.java
new file mode 100644
index 0000000..b237327
--- /dev/null
+++ b/src/com/android/dialer/lookup/whitepages/WhitePagesPeopleLookup.java
@@ -0,0 +1,78 @@
+/*
+ * 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.whitepages;
+
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.PeopleLookup;
+
+import android.content.Context;
+import android.location.Location;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class WhitePagesPeopleLookup extends PeopleLookup {
+ private static final String TAG =
+ WhitePagesPeopleLookup.class.getSimpleName();
+
+ public WhitePagesPeopleLookup(Context context) {
+ }
+
+ @Override
+ public ContactInfo[] lookup(Context context, String filter) {
+ WhitePagesApi.ContactInfo[] infos = null;
+
+ try {
+ infos = WhitePagesApi.peopleLookup(context, filter, 3);
+ } catch (IOException e) {
+ Log.e(TAG, "People lookup failed", e);
+ }
+
+ if (infos == null || infos.length == 0) {
+ return null;
+ }
+
+ ContactInfo[] details = new ContactInfo[infos.length];
+ for (int i = 0; i < infos.length; i++) {
+ WhitePagesApi.ContactInfo info = infos[i];
+ ContactBuilder builder = new ContactBuilder(
+ ContactBuilder.PEOPLE_LOOKUP, null, info.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 || info.city != null) {
+ ContactBuilder.Address a = new ContactBuilder.Address();
+ a.city = info.city;
+ a.formattedAddress = info.address;
+ a.type = StructuredPostal.TYPE_HOME;
+ builder.addAddress(a);
+ }
+
+ details[i] = builder.build();
+ }
+
+ return details;
+ }
+}
diff --git a/src/com/android/dialer/lookup/whitepages/WhitePagesReverseLookup.java b/src/com/android/dialer/lookup/whitepages/WhitePagesReverseLookup.java
new file mode 100644
index 0000000..375c63f
--- /dev/null
+++ b/src/com/android/dialer/lookup/whitepages/WhitePagesReverseLookup.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.whitepages;
+
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+import com.android.dialer.lookup.ReverseLookup;
+
+import android.content.Context;
+
+import java.io.IOException;
+
+public class WhitePagesReverseLookup extends ReverseLookup {
+ private static final String TAG =
+ WhitePagesReverseLookup.class.getSimpleName();
+
+ public WhitePagesReverseLookup(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 {
+ WhitePagesApi.ContactInfo info = WhitePagesApi.reverseLookup(context, normalizedNumber);
+ if (info == null || 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/src/com/android/dialer/lookup/yellowpages/YellowPagesApi.java b/src/com/android/dialer/lookup/yellowpages/YellowPagesApi.java
new file mode 100644
index 0000000..b52a67b
--- /dev/null
+++ b/src/com/android/dialer/lookup/yellowpages/YellowPagesApi.java
@@ -0,0 +1,222 @@
+/*
+ * 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 org.apache.http.client.methods.HttpGet;
+
+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 =
+ "http://www.yellowpages.com/phone?phone_search_terms=";
+ private static final String LOOKUP_URL_CANADA =
+ "http://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(new HttpGet(mLookupUrl + mNumber));
+ }
+
+ private String getPhotoUrl(String website) throws IOException {
+ String output = LookupUtils.httpGet(new HttpGet(website));
+ String galleryRef = LookupUtils.firstRegexResult(output,
+ "href=\"([^\"]+gallery\\?lid=[^\"]+)\"", true);
+ if (galleryRef == null) {
+ return null;
+ }
+
+ // Get first image
+ HttpGet get = new HttpGet("http://www.yellowpages.com" + galleryRef);
+ output = LookupUtils.httpGet(get);
+
+ return LookupUtils.firstRegexResult(output,
+ "\"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 = "http://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/src/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java b/src/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java
new file mode 100644
index 0000000..eaaee57
--- /dev/null
+++ b/src/com/android/dialer/lookup/yellowpages/YellowPagesReverseLookup.java
@@ -0,0 +1,137 @@
+/*
+ * 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.calllog.ContactInfo;
+import com.android.dialer.lookup.ContactBuilder;
+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 org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+import java.io.ByteArrayOutputStream;
+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")) {
+ HttpClient client = new DefaultHttpClient();
+ HttpGet request = new HttpGet(uri.toString());
+
+ try {
+ HttpResponse response = client.execute(request);
+
+ int responseCode = response.getStatusLine().getStatusCode();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ response.getEntity().writeTo(out);
+ byte[] responseBytes = out.toByteArray();
+
+ if (responseCode == HttpStatus.SC_OK) {
+ Bitmap bmp = BitmapFactory.decodeByteArray(
+ responseBytes, 0, responseBytes.length);
+ return bmp;
+ }
+ } 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/src/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java b/src/com/android/dialer/lookup/zabasearch/ZabaSearchApi.java
new file mode 100644
index 0000000..2e8b65a
--- /dev/null
+++ b/src/com/android/dialer/lookup/zabasearch/ZabaSearchApi.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.zabasearch;
+
+import android.text.TextUtils;
+
+import com.android.dialer.lookup.LookupUtils;
+
+import org.apache.http.client.methods.HttpGet;
+
+import java.io.IOException;
+
+public class ZabaSearchApi {
+ private static final String TAG = ZabaSearchApi.class.getSimpleName();
+
+ private static final String LOOKUP_URL = "http://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(new HttpGet(LOOKUP_URL + mNumber));
+ }
+
+ 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/src/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java b/src/com/android/dialer/lookup/zabasearch/ZabaSearchReverseLookup.java
new file mode 100644
index 0000000..afe9961
--- /dev/null
+++ b/src/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.calllog.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/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index 35eba4b..e9c0909 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -59,6 +59,12 @@ public class DialerSettingsActivity extends PreferenceActivity {
TelephonyManager telephonyManager =
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ final Header lookupSettingsHeader = new Header();
+ lookupSettingsHeader.titleRes = R.string.lookup_settings_label;
+ lookupSettingsHeader.summaryRes = R.string.lookup_settings_description;
+ lookupSettingsHeader.fragment = LookupSettingsFragment.class.getName();
+ target.add(lookupSettingsHeader);
+
// Only show call setting menus if the current user is the primary/owner user.
if (isPrimaryUser()) {
// Show "Call Settings" if there is one SIM and "Phone Accounts" if there are more.
diff --git a/src/com/android/dialer/settings/LookupSettingsFragment.java b/src/com/android/dialer/settings/LookupSettingsFragment.java
new file mode 100644
index 0000000..1621c92
--- /dev/null
+++ b/src/com/android/dialer/settings/LookupSettingsFragment.java
@@ -0,0 +1,166 @@
+/*
+ * 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.settings;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+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 cyanogenmod.providers.CMSettings;
+
+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);
+
+ updateReverseLookupProviderList();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ restoreLookupProviderSwitches();
+ restoreLookupProviders();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final ContentResolver cr = getActivity().getContentResolver();
+
+ if (preference == mEnableForwardLookup) {
+ CMSettings.System.putInt(cr, CMSettings.System.ENABLE_FORWARD_LOOKUP,
+ ((Boolean) newValue) ? 1 : 0);
+ } else if (preference == mEnablePeopleLookup) {
+ CMSettings.System.putInt(cr, CMSettings.System.ENABLE_PEOPLE_LOOKUP,
+ ((Boolean) newValue) ? 1 : 0);
+ } else if (preference == mEnableReverseLookup) {
+ CMSettings.System.putInt(cr, CMSettings.System.ENABLE_REVERSE_LOOKUP,
+ ((Boolean) newValue) ? 1 : 0);
+ } else if (preference == mForwardLookupProvider) {
+ CMSettings.System.putString(cr, CMSettings.System.FORWARD_LOOKUP_PROVIDER,
+ (String) newValue);
+ } else if (preference == mPeopleLookupProvider) {
+ CMSettings.System.putString(cr, CMSettings.System.PEOPLE_LOOKUP_PROVIDER,
+ (String) newValue);
+ } else if (preference == mReverseLookupProvider) {
+ CMSettings.System.putString(cr, CMSettings.System.REVERSE_LOOKUP_PROVIDER,
+ (String) newValue);
+ }
+
+ return true;
+ }
+
+ private void updateReverseLookupProviderList() {
+ Resources res = getResources();
+
+ String[] entries = res.getStringArray(R.array.reverse_lookup_provider_names);
+ String[] values = res.getStringArray(R.array.reverse_lookup_providers);
+
+ if (isPackageInstalled(getString(R.string.cyngn_reverse_lookup_provider_package))) {
+ entries = Arrays.copyOf(entries, entries.length + 1);
+ values = Arrays.copyOf(values, values.length + 1);
+
+ entries[entries.length - 1] = getString(R.string.cyngn_reverse_lookup_provider_name);
+ values[values.length - 1] = getString(R.string.cyngn_reverse_lookup_provider_value);
+ }
+
+ mReverseLookupProvider.setEntries(entries);
+ mReverseLookupProvider.setEntryValues(values);
+ }
+
+ private boolean isPackageInstalled(String pkg) {
+ try {
+ PackageInfo pi = getActivity().getPackageManager().getPackageInfo(pkg, 0);
+ return pi.applicationInfo.enabled;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void restoreLookupProviderSwitches() {
+ final ContentResolver cr = getActivity().getContentResolver();
+ mEnableForwardLookup.setChecked(CMSettings.System.getInt(cr,
+ CMSettings.System.ENABLE_FORWARD_LOOKUP, 1) != 0);
+ mEnablePeopleLookup.setChecked(CMSettings.System.getInt(cr,
+ CMSettings.System.ENABLE_PEOPLE_LOOKUP, 1) != 0);
+ mEnableReverseLookup.setChecked(CMSettings.System.getInt(cr,
+ CMSettings.System.ENABLE_REVERSE_LOOKUP, 1) != 0);
+ }
+
+ private void restoreLookupProviders() {
+ restoreLookupProvider(mForwardLookupProvider, CMSettings.System.FORWARD_LOOKUP_PROVIDER);
+ restoreLookupProvider(mPeopleLookupProvider, CMSettings.System.PEOPLE_LOOKUP_PROVIDER);
+ restoreLookupProvider(mReverseLookupProvider, CMSettings.System.REVERSE_LOOKUP_PROVIDER);
+ }
+
+ private void restoreLookupProvider(ListPreference pref, String key) {
+ final ContentResolver cr = getActivity().getContentResolver();
+ String provider = CMSettings.System.getString(cr, key);
+ if (provider == null) {
+ pref.setValueIndex(0);
+ CMSettings.System.putString(cr, key, pref.getValue());
+ } else {
+ pref.setValue(provider);
+ }
+ }
+}