summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/raw/preloaded_contacts.json0
-rw-r--r--res/raw/preloaded_contacts_schema.json59
-rw-r--r--res/values-ast-rES/strings.xml55
-rw-r--r--res/values-be/strings.xml39
-rw-r--r--res/values-br-rFR/strings.xml39
-rw-r--r--res/values-cy/strings.xml39
-rw-r--r--res/values-en-rPT/strings.xml39
-rw-r--r--res/values-eo/strings.xml39
-rw-r--r--res/values-es-rMX/strings.xml39
-rw-r--r--res/values-fy-rNL/strings.xml39
-rw-r--r--res/values-gd-rGB/strings.xml39
-rw-r--r--res/values-ku/strings.xml55
-rw-r--r--res/values-lb/strings.xml55
-rw-r--r--res/values-oc-rFR/strings.xml39
-rw-r--r--res/values-ug/strings.xml49
-rwxr-xr-x[-rw-r--r--]res/values-zh-rCN/strings.xml4
-rw-r--r--res/values/config.xml39
-rwxr-xr-x[-rw-r--r--]res/values/strings.xml3
-rw-r--r--src/com/android/providers/contacts/AccountWithDataSet.java8
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java5
-rw-r--r--src/com/android/providers/contacts/Constants.java3
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java105
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java509
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java18
-rw-r--r--src/com/android/providers/contacts/LegacyApiSupport.java9
-rw-r--r--src/com/android/providers/contacts/util/PreloadedContactsFileParser.java225
26 files changed, 1519 insertions, 33 deletions
diff --git a/res/raw/preloaded_contacts.json b/res/raw/preloaded_contacts.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/raw/preloaded_contacts.json
diff --git a/res/raw/preloaded_contacts_schema.json b/res/raw/preloaded_contacts_schema.json
new file mode 100644
index 0000000..384d87f
--- /dev/null
+++ b/res/raw/preloaded_contacts_schema.json
@@ -0,0 +1,59 @@
+/*
+ This file encodes the contact information that must be preloaded into
+ the contacts database. The contact information is encoded according
+ to the schema outlined below.
+
+ The top-level object is an array of 'contacts'. Each of the array elements
+ is a contact with an array called 'data' defined within. Each of the objects
+ within 'data' objects describe an aspect of the contact - name, phone number,
+ address etc. Each aspect becomes a row in the raw_contacts table within
+ the contacts database.
+
+ When describing a property for a contact aspect, the keys and values can
+ reference java fields. These fields will be resolved at runtime. This enables
+ us to leverage the fields defined in android.provider.ContactsContract.* classes
+ to describe the contact information.
+
+ Note that if any java fields are referenced, they must be fully qualified names
+ as seen from a ClassLoader's perspective. There is no validation done to ensure
+ that the fields names can be resolved till runtime.
+
+ Conveniences afforded while declaring contact information :
+ @ = android.provider.ContactsContract$CommonDataKinds
+ @mimetype = android.provider.ContactsContract$Data.MIMETYPE
+
+ Example :
+ {
+ "contacts": [
+ {
+ "data": [
+ {
+ "@mimetype": "{{@$StructuredName.CONTENT_ITEM_TYPE}}",
+ "@$StructuredName.DISPLAY_NAME": "John Doe"
+ },
+
+ {
+ "@mimetype": "{{@$Phone.CONTENT_ITEM_TYPE}}",
+ "@$Phone.NUMBER": "123-456-7890",
+ "@$Phone.TYPE": "{{@$Phone.TYPE_WORK}}"
+ }
+ ]
+ }
+ ]
+ }
+
+ Property values can be static values or expressions that need to be evaluated, like
+ the property keys. Values are interpolated when enclosed within
+ double-curly-braces - '{{' - akin to a templating system like Handlebars.
+
+ Limitations :
+ - currently, for property values, there is no provision to embed an
+ expression-to-be-evaluated, within a larger string
+ ex. "{{com.example.foo.BAR}} additional content" isn't valid
+
+ - lack of compile time validation of the java fields specified or of the syntax
+
+ - lack of compile time validation of the property keys' with-respect-to the
+ columns of the raw_contacts table
+
+*/ \ No newline at end of file
diff --git a/res/values-ast-rES/strings.xml b/res/values-ast-rES/strings.xml
new file mode 100644
index 0000000..2f8bdef
--- /dev/null
+++ b/res/values-ast-rES/strings.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <string name="sharedUserLabel">Aplicaciones básiques d\'Android</string>
+ <!-- This is the label for the application that stores contacts data -->
+ <string name="app_label">Información de los contautos</string>
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <string name="provider_label">Contautos</string>
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_ticker">L\'anovamientu de contautos necesita más memoria.</string>
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_title">Anovando almacenamientu pa contautos</string>
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_text">Tocar pa completar l\'anovamientu.</string>
+ <!-- The name of the default contact directory -->
+ <string name="default_directory">Contautos</string>
+ <!-- The name of the invisible local contact directory -->
+ <string name="local_invisible_directory">Otru</string>
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <string name="voicemail_from_column">"Mensaxe de voz de "</string>
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_title">Copiar base de datos de contautos</string>
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_database_message">Tas a piques de 1) facer una copia de la to base de datos, qu\'inclúi la información rellacionada colos contautos y el rexistru de llamaes nel almacenamientu internu y a 2) unviala per corréu-e. Nun escaezas desaniciar la copia en cuantes la tengas copiada n\'otru preséu o tengas recibío\'l corréu-e.</string>
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_delete_button">Desaniciar agora</string>
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_start_button">Aniciar</string>
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_sender_picker">Esbilla un programa pa unviar el ficheru</string>
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_subject">Base datos de contautos axuntada</string>
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_body">La mio base de datos de contautos con tola información de contautos, va axunta a esti mensaxe. Usa esta información con curiáu.</string>
+</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-br-rFR/strings.xml b/res/values-br-rFR/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-br-rFR/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-cy/strings.xml b/res/values-cy/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-cy/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-en-rPT/strings.xml b/res/values-en-rPT/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-en-rPT/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-eo/strings.xml b/res/values-eo/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-eo/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-es-rMX/strings.xml b/res/values-es-rMX/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-es-rMX/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-fy-rNL/strings.xml b/res/values-fy-rNL/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-fy-rNL/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-gd-rGB/strings.xml b/res/values-gd-rGB/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-gd-rGB/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-ku/strings.xml b/res/values-ku/strings.xml
new file mode 100644
index 0000000..d2cb640
--- /dev/null
+++ b/res/values-ku/strings.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <string name="sharedUserLabel">نەرمەواڵاکانی ناوەکی ئەندرۆید</string>
+ <!-- This is the label for the application that stores contacts data -->
+ <string name="app_label">بیرگەی ناوەکان</string>
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <string name="provider_label">ناوەکان</string>
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_ticker">نوێکاری ناوەکان پێویستی بە بیرگەی زۆرترە.</string>
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_title">تازەکردنەوەی بیرگە بۆ ناوەکان</string>
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_text">دەست لێدە بۆ تەواوکردنی نوێکارییەکە.</string>
+ <!-- The name of the default contact directory -->
+ <string name="default_directory">ناوەکان</string>
+ <!-- The name of the invisible local contact directory -->
+ <string name="local_invisible_directory">دیکە</string>
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <string name="voicemail_from_column">"دەنگنامە لەلایەن "</string>
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_title">کۆپی کردنی بنکەدراوەی ناوەکان</string>
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_database_message">تۆ بە تەمای 1) کۆپی گرتنەوە لە بنکەدراوەکەتی کە هەموو ناوەکان بە تۆماری پەیوەندی و زانیارییەکانییەوە لەخۆ ئەگرێت، بۆ ناو بیرگەی ناوەکی , هەروەها 2) ئیمێڵ کردنیشی. لەیادت بێ کۆپییەکە بسڕیتەوە هەر کە بە سەرکەوتووانە کۆپیت کردەوە بۆ ئامێرەکەت یان ئیمێڵەکە وەرگیردرا.</string>
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_delete_button">سڕینەوە لە ئێستادا</string>
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_start_button">دەستپێکردن</string>
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_sender_picker">بەرنامەیەک هەڵبژێرە بۆ ناردنی پەڕگەکەت</string>
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_subject">بنکەدراوەی ناوەکان پێوەلکێندرا</string>
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_body">پێوەلکێندراوەکە بنکەدراوەی ناوەکانمە و لەگەڵ هەموو زانیاری ناوەکانم. بە وریایی بیهێڵەرەوە.</string>
+</resources>
diff --git a/res/values-lb/strings.xml b/res/values-lb/strings.xml
new file mode 100644
index 0000000..9017bb4
--- /dev/null
+++ b/res/values-lb/strings.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <string name="sharedUserLabel">Kär-Appe vun Android</string>
+ <!-- This is the label for the application that stores contacts data -->
+ <string name="app_label">Späicher fir Kontakter</string>
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <string name="provider_label">Kontakter</string>
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_ticker">D\'Aktualiséierung vun de Kontakter brauch méi Späicher.</string>
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_title">Späicher fir Kontakter gëtt aktualiséiert</string>
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_text">Drécke fir d\'Aktualiséierung ofzeschléissen.</string>
+ <!-- The name of the default contact directory -->
+ <string name="default_directory">Kontakter</string>
+ <!-- The name of the invisible local contact directory -->
+ <string name="local_invisible_directory">Aner</string>
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <string name="voicemail_from_column">"Sproochmessage vum "</string>
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_title">Kontaktdatebank kopéieren</string>
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_database_message">Du bass am Gaang 1) eng Kopie vun denger Datebank an den interne Späicher ze maachen an där all d\'Informatioune vun de Kontakter an den Historique vun allen Telefonsgespréicher stinn, an 2) se ze mailen. Denk drun d\'Kopie ze läsche soubal s du s\'erfollegräich vum Apparat kopéiert hues oder d\'E-Mail ukomm ass.</string>
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_delete_button">Elo läschen</string>
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_start_button">Start</string>
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_sender_picker">Wiel e Programm aus fir däi Fichier ze schécken</string>
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_subject">Kontakt-DB ugehaangen</string>
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_body">Ugehaangen ass meng Kontaktdatebank mat all menge Kontaktinformatiounen. Mat Virsiicht behandelen.</string>
+</resources>
diff --git a/res/values-oc-rFR/strings.xml b/res/values-oc-rFR/strings.xml
new file mode 100644
index 0000000..03e136d
--- /dev/null
+++ b/res/values-oc-rFR/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <!-- This is the label for the application that stores contacts data -->
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+</resources>
diff --git a/res/values-ug/strings.xml b/res/values-ug/strings.xml
new file mode 100644
index 0000000..168b641
--- /dev/null
+++ b/res/values-ug/strings.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for this app's shared user ID (and thus for all other
+ apps sharing the uid). -->
+ <string name="sharedUserLabel">ئاندىرويىد يادرولۇق ئەپلەر</string>
+ <!-- This is the label for the application that stores contacts data -->
+ <string name="app_label">ئالاقەداش ساقلىغۇچ</string>
+ <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
+ <!-- Ticker for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_ticker">ئالاقەداشلار ساندانىنى يۈكسەلدۈرۈشكە تېخىمۇ كۆپ ئەسلەك كېرەك.</string>
+ <!-- Title for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_title">ئالاقەداشلارنى ساقلىغۇچتىن يۈكسەلدۈرۈش</string>
+ <!-- Text for the notification shown when updating contacts fails because of memory shortage -->
+ <string name="upgrade_out_of_memory_notification_text">چېكىلسە يۈكسەلدۈرۈشنى تاماملايدۇ.</string>
+ <!-- The name of the default contact directory -->
+ <!-- The name of the invisible local contact directory -->
+ <!-- The prefix string before the number used for the display name for the voicemail table.
+ Because of the way this is combined in SQL we can't allow a generic format string.
+ The resulting string will be this string with the number appended to the end.
+ Note that the trailing space is important, and that to achieve it we have to wrap the
+ string in double quotes. -->
+ <string name="voicemail_from_column">"ئۈنخەت ئورنى "</string>
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_title">ئالاقەداشلار ساندانىنى كۆچۈر</string>
+ <!-- Debug tool - message shown to the user on the dialog which sends a copy of the contact database via email or other apps. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_database_message">سىز 1)ھەممە ئالاقەداشلىرىڭىزغا مۇناسىۋەتلىك ئۇچۇرلار ۋە چاقىرىش خاتىرىسىنى ئۆز ئىچىگە ئالغان ساندانىڭىزنىڭ كۆچۈرۈلمە نۇسخىسىنى تاييارلاپ، ئىچىدىكى ساقلىغۇچقا ساقلايدۇ، 2)تورخەت يوللىيالايدۇ. ئۈسكۈنىڭىزدىن كۆچۈرۈلمە نۇسخىنى كۆچۈرۈۋالغان ياكى تورخەتنى تاپشۇرۇۋالغاندىن كېيىن كۆچۈرۈلمە نۇسخىسىنى ئامالنىڭ بارىچە تېزراق ئۆچۈرۈۋېتىڭ.</string>
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_subject">ئالاقەداشلار ساندانى قوشۇلدى</string>
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_body">قوشۇلغىنى مېنىڭ ھەممە ئالاقەداشلار ئۇچۇرۇمنى ئۆز ئىچىگە ئالغان ئالاقەداشلار ساندانىم. ئېھتىيات بىلەن ساقلاڭ.</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index ad4eda6..c3754e8 100644..100755
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -32,4 +32,8 @@
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"选择用于发送文件的程序"</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"随附通讯录数据库"</string>
<string name="debug_dump_email_body" msgid="4577749800871444318">"附件是我的通讯录数据库,其中包含我所有的联系人信息,因此请谨慎处理。"</string>
+
+ <string name="group_title_co_workers">"同事"</string>
+ <string name="group_title_family">"家人"</string>
+ <string name="group_title_friends">"朋友"</string>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..4c97cb8
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015, The Linux Foundation. All Rights Reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-->
+
+<resources>
+
+ <!-- If true, it supports fuzzy search for contacts number -->
+ <bool name="phone_number_fuzzy_search">false</bool>
+
+ <!-- request to attempt preload-ing contacts -->
+ <bool name="config_preload_contacts">false</bool>
+
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8be7bca..5c0ef96 100644..100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -69,4 +69,7 @@
<!-- Debug tool - email body [CHAR LIMIT=NONE] -->
<string name="debug_dump_email_body">Attached is my contacts database with all my contacts information. Handle with care.</string>
+ <string name="group_title_co_workers">"Coworkers"</string>
+ <string name="group_title_family">"Family"</string>
+ <string name="group_title_friends">"Friends"</string>
</resources>
diff --git a/src/com/android/providers/contacts/AccountWithDataSet.java b/src/com/android/providers/contacts/AccountWithDataSet.java
index bfae112..a654f50 100644
--- a/src/com/android/providers/contacts/AccountWithDataSet.java
+++ b/src/com/android/providers/contacts/AccountWithDataSet.java
@@ -25,7 +25,11 @@ import com.google.common.base.Objects;
* Account information that includes the data set, if any.
*/
public class AccountWithDataSet {
- public static final AccountWithDataSet LOCAL = new AccountWithDataSet(null, null, null);
+ public static final String PHONE_NAME = "PHONE";
+ public static final String ACCOUNT_TYPE_PHONE = "com.android.localphone";
+ public static final String ACCOUNT_TYPE_SIM = "com.android.sim";
+ public static final AccountWithDataSet LOCAL = new AccountWithDataSet(
+ PHONE_NAME, ACCOUNT_TYPE_PHONE, null);
private final String mAccountName;
private final String mAccountType;
@@ -62,7 +66,7 @@ public class AccountWithDataSet {
}
public boolean isLocalAccount() {
- return (mAccountName == null) && (mAccountType == null);
+ return (PHONE_NAME.equals(mAccountName)) && (ACCOUNT_TYPE_PHONE.equals(mAccountType));
}
@Override
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index f83e4a7..9070e78 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -49,6 +49,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.util.SelectionBuilder;
import com.android.providers.contacts.util.UserUtils;
+
import com.google.common.annotations.VisibleForTesting;
import java.util.HashMap;
@@ -364,6 +365,10 @@ public class CallLogProvider extends ContentProvider {
case CALLS:
return getDatabaseModifier(db).delete(Tables.CALLS,
selectionBuilder.build(), selectionArgs);
+ case CALLS_ID:
+ return getDatabaseModifier(db).delete(Tables.CALLS,
+ new SelectionBuilder(Calls._ID + "=?").build(),
+ new String[] { uri.getLastPathSegment() });
default:
throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
}
diff --git a/src/com/android/providers/contacts/Constants.java b/src/com/android/providers/contacts/Constants.java
index 8cf28e6..f828d74 100644
--- a/src/com/android/providers/contacts/Constants.java
+++ b/src/com/android/providers/contacts/Constants.java
@@ -21,4 +21,7 @@ public class Constants {
// Log tag for performance measurement.
// To enable: adb shell setprop log.tag.ContactsPerf VERBOSE
public static final String PERFORMANCE_TAG = "ContactsPerf";
+
+ // log info while preloading `default` contacts
+ public static final String TAG_DEBUG_PRELOAD_CONTACTS = "PreloadContacts";
}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index aac37ba..c84c97f 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -285,11 +285,11 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
*/
public static final String GROUP_MEMBER_COUNT =
" LEFT OUTER JOIN (SELECT "
- + "data.data1 AS member_count_group_id, "
- + "COUNT(data.raw_contact_id) AS group_member_count "
- + "FROM data "
+ + "view_data.data1 AS member_count_group_id, "
+ + "COUNT(DISTINCT view_data.contact_id) AS group_member_count "
+ + "FROM view_data "
+ "WHERE "
- + "data.mimetype_id = (SELECT _id FROM mimetypes WHERE "
+ + "view_data.mimetype_id = (SELECT _id FROM mimetypes WHERE "
+ "mimetypes.mimetype = '" + GroupMembership.CONTENT_ITEM_TYPE + "')"
+ "GROUP BY member_count_group_id) AS member_count_table" // End of inner query
+ " ON (groups._id = member_count_table.member_count_group_id)";
@@ -335,9 +335,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
"(SELECT " + AccountsColumns._ID +
" FROM " + Tables.ACCOUNTS +
" WHERE " +
- AccountsColumns.ACCOUNT_NAME + " IS NULL AND " +
- AccountsColumns.ACCOUNT_TYPE + " IS NULL AND " +
- AccountsColumns.DATA_SET + " IS NULL)";
+ AccountsColumns.ACCOUNT_TYPE + "='" +
+ AccountWithDataSet.ACCOUNT_TYPE_PHONE + "')";
final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_ID
+ "=" + LOCAL_ACCOUNT_ID;
@@ -351,8 +350,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
"SELECT " +
"MAX((SELECT (CASE WHEN " +
"(CASE" +
- " WHEN " + RAW_CONTACT_IS_LOCAL +
- " THEN 1 " +
" WHEN " + ZERO_GROUP_MEMBERSHIPS +
" THEN " + Settings.UNGROUPED_VISIBLE +
" ELSE MAX(" + Groups.GROUP_VISIBLE + ")" +
@@ -1600,6 +1597,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
// Add the legacy API support views, etc.
LegacyApiSupport.createDatabase(db);
+ createPhoneAccount(db);
+ createDefaultGroups4PhoneAccount(db);
+
if (mDatabaseOptimizationEnabled) {
// This will create a sqlite_stat1 table that is used for query optimization
db.execSQL("ANALYZE;");
@@ -2022,13 +2022,18 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
String contactsSelect = "SELECT "
+ ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
+ contactsColumns + ", "
+ + AccountsColumns.ACCOUNT_NAME + ", "
+ + AccountsColumns.ACCOUNT_TYPE + ", "
+ buildDisplayPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_URI) + ", "
+ buildThumbnailPhotoUriAlias(ContactsColumns.CONCRETE_ID,
Contacts.PHOTO_THUMBNAIL_URI) + ", "
+ dbForProfile() + " AS " + Contacts.IS_USER_PROFILE
+ " FROM " + Tables.CONTACTS
+ " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
- + Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")";
+ + Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
+ + " JOIN " + Tables.ACCOUNTS + " AS name_accounts ON("
+ + "name_raw_contact." + RawContactsColumns.ACCOUNT_ID + "=name_accounts."
+ + AccountsColumns._ID + ")";
db.execSQL("CREATE VIEW " + Views.CONTACTS + " AS " + contactsSelect);
@@ -3476,6 +3481,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
insertNameLookup(db);
rebuildSortKeys(db);
createContactsIndexes(db, rebuildSqliteStats);
+ rebuildDefaultGroupTitles(db, locales.getPrimaryLocale());
FastScrollingIndexCache.getInstance(mContext).invalidate();
// Update the ICU version used to generate the locale derived data
@@ -3485,6 +3491,35 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
/**
+ * change the default groups' title according to the locale
+ */
+ private void rebuildDefaultGroupTitles(SQLiteDatabase db, Locale locale) {
+ String[] PROJECTION = new String[] {Groups._ID, Groups.TITLE_RES};
+ Cursor cursor = db.query(Tables.GROUPS, PROJECTION, Groups.TITLE_RES + " IS NOT NULL ", null
+ ,null, null, Groups._ID);
+
+ if (cursor == null) {
+ return;
+ }
+
+ try {
+ long groupId = -1;
+ int titleRes = 0;
+ ContentValues values = new ContentValues();
+ while (cursor.moveToNext()) {
+ groupId = cursor.getLong(0);
+ titleRes = cursor.getInt(1);
+ values.clear();
+ values.put(Groups.TITLE, mContext.getResources().getString(titleRes));
+ db.update(Tables.GROUPS, values, Groups._ID + " = ?", new String[] {
+ String.valueOf(groupId)});
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
* Regenerates all locale-sensitive data if needed:
* nickname_lookup, name_lookup and sort keys. Invalidates the fast
* scrolling index cache.
@@ -5987,4 +6022,54 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
new String[] {String.valueOf(contactId)});
}
+
+ private void createDefaultGroups4PhoneAccount(SQLiteDatabase db) {
+ // 3 default groups
+ String[] title = new String[3];
+ int[] titleRes = new int[3];
+
+ title[0] = mContext.getResources().getString(R.string.group_title_co_workers);
+ titleRes[0] = R.string.group_title_co_workers;
+
+ title[1] = mContext.getResources().getString(R.string.group_title_family);
+ titleRes[1] = R.string.group_title_family;
+
+ title[2] = mContext.getResources().getString(R.string.group_title_friends);
+ titleRes[2] = R.string.group_title_friends;
+
+ for (int i = 0; i < title.length; i++) {
+ db.execSQL("INSERT INTO " + Tables.GROUPS + " (" +
+ GroupsColumns.ACCOUNT_ID + "," +
+ Groups.SOURCE_ID + "," +
+ Groups.VERSION + "," +
+ Groups.DIRTY + "," +
+ Groups.TITLE + "," +
+ Groups.TITLE_RES + "," +
+ Groups.NOTES + "," +
+ Groups.SYSTEM_ID + "," +
+ Groups.DELETED + "," +
+ Groups.GROUP_VISIBLE + "," +
+ Groups.SHOULD_SYNC + "," +
+ Groups.AUTO_ADD + "," +
+ Groups.FAVORITES + "," +
+ Groups.GROUP_IS_READ_ONLY + "," +
+ Groups.SYNC1 + ", " +
+ Groups.SYNC2 + ", " +
+ Groups.SYNC3 + ", " +
+ Groups.SYNC4 + ") " +
+ "VALUES (1,1,1,0,'"
+ + title[i] + "'," + titleRes[i] +
+ ",NULL,NULL,0,1,1,0,0,1,'','','','');"
+ );
+ }
+ }
+
+ public void createPhoneAccount(SQLiteDatabase db) {
+ String sql = "INSERT INTO " + Tables.ACCOUNTS
+ + "(" + AccountsColumns.ACCOUNT_NAME + ","
+ + AccountsColumns.ACCOUNT_TYPE + ") "
+ + "VALUES ('" + AccountWithDataSet.PHONE_NAME + "','"
+ + AccountWithDataSet.ACCOUNT_TYPE_PHONE + "')";
+ db.execSQL(sql);
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 8904dc1..67719cc 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -158,6 +158,7 @@ import com.android.providers.contacts.database.MoreDatabaseUtils;
import com.android.providers.contacts.util.Clock;
import com.android.providers.contacts.util.ContactsPermissions;
import com.android.providers.contacts.util.DbQueryUtils;
+import com.android.providers.contacts.util.PreloadedContactsFileParser;
import com.android.providers.contacts.util.NeededForTesting;
import com.android.providers.contacts.util.UserUtils;
import com.android.vcard.VCardComposer;
@@ -175,6 +176,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
@@ -193,6 +195,8 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import org.json.JSONException;
+
/**
* Contacts content provider. The contract between this provider and applications
* is defined in {@link ContactsContract}.
@@ -240,12 +244,15 @@ public class ContactsProvider2 extends AbstractContactsProvider
private static final int BACKGROUND_TASK_CHANGE_LOCALE = 9;
private static final int BACKGROUND_TASK_CLEANUP_PHOTOS = 10;
private static final int BACKGROUND_TASK_CLEAN_DELETE_LOG = 11;
+ private static final int BACKGROUND_TASK_ADD_DEFAULT_CONTACT = 12;
protected static final int STATUS_NORMAL = 0;
protected static final int STATUS_UPGRADING = 1;
protected static final int STATUS_CHANGING_LOCALE = 2;
protected static final int STATUS_NO_ACCOUNTS_NO_CONTACTS = 3;
+ private static final String PREF_PRELOADED_CONTACTS_ADDED = "preloaded_contacts_added";
+
/** Default for the maximum number of returned aggregation suggestions. */
private static final int DEFAULT_MAX_SUGGESTIONS = 5;
@@ -300,6 +307,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
private static final String FREQUENT_ORDER_BY = DataUsageStatColumns.TIMES_USED + " DESC,"
+ Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
+ private static String WITHOUT_SIM_FLAG = "no_sim";
+
+ private boolean isWhereAppended = false;
+
+ public static final String ADD_GROUP_MEMBERS = "add_group_members";
private static final int CONTACTS = 1000;
private static final int CONTACTS_ID = 1001;
@@ -532,6 +544,22 @@ public class ContactsProvider2 extends AbstractContactsProvider
+ " FROM " + Tables.GROUPS
+ " WHERE " + Groups.TITLE + "=?)))";
+ private static final String CONTACTS_IN_GROUP_ID_SELECT =
+ Contacts._ID + " IN "
+ + "(SELECT DISTINCT "
+ + Data.CONTACT_ID
+ + " FROM " + Views.DATA
+ + " WHERE " + Data.MIMETYPE + " = '" + GroupMembership.CONTENT_ITEM_TYPE
+ + "' AND " + Data.DATA1 + " = ?)";
+
+ private static final String CONTACTS_NOT_IN_GROUP_ID_SELECT =
+ Contacts._ID + " NOT IN "
+ + "(SELECT DISTINCT "
+ + Data.CONTACT_ID
+ + " FROM " + Views.DATA
+ + " WHERE " + Data.MIMETYPE + " = '" + GroupMembership.CONTENT_ITEM_TYPE
+ + "' AND " + Data.DATA1 + " = ?)";
+
/** Sql for updating DIRTY flag on multiple raw contacts */
private static final String UPDATE_RAW_CONTACT_SET_DIRTY_SQL =
"UPDATE " + Tables.RAW_CONTACTS +
@@ -771,6 +799,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.add(Contacts.IS_USER_PROFILE)
.addAll(sContactsColumns)
.addAll(sContactsPresenceColumns)
+ .add(RawContacts.ACCOUNT_TYPE)
+ .add(RawContacts.ACCOUNT_NAME)
.build();
/** Contains just the contacts columns */
@@ -917,6 +947,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
.addAll(sDataColumns)
.addAll(sDataPresenceColumns)
.addAll(sContactsColumns)
+ .addAll(sRawContactColumns)
.addAll(sContactPresenceColumns)
.addAll(sDataUsageColumns)
.build();
@@ -1203,6 +1234,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
matcher.addURI(ContactsContract.AUTHORITY, "contacts/strequent/", CONTACTS_STREQUENT);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/strequent/filter/*",
CONTACTS_STREQUENT_FILTER);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/group", CONTACTS_GROUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/group/*", CONTACTS_GROUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/frequent", CONTACTS_FREQUENT);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/delete_usage", CONTACTS_DELETE_USAGE);
@@ -1451,6 +1483,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private Handler mBackgroundHandler;
private long mLastPhotoCleanup = 0;
+ private boolean isPhoneNumberFuzzySearchEnabled;
private FastScrollingIndexCache mFastScrollingIndexCache;
@@ -1524,6 +1557,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
profileInfo.authority = ContactsContract.AUTHORITY;
mProfileProvider.attachInfo(getContext(), profileInfo);
mProfileHelper = mProfileProvider.getDatabaseHelper(getContext());
+ isPhoneNumberFuzzySearchEnabled = getContext().getResources().getBoolean(
+ R.bool.phone_number_fuzzy_search);
// Initialize the pre-authorized URI duration.
mPreAuthorizedUriDuration = DEFAULT_PREAUTHORIZED_URI_EXPIRATION;
@@ -1537,6 +1572,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
scheduleBackgroundTask(BACKGROUND_TASK_OPEN_WRITE_ACCESS);
scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
scheduleBackgroundTask(BACKGROUND_TASK_CLEAN_DELETE_LOG);
+ scheduleBackgroundTask(BACKGROUND_TASK_ADD_DEFAULT_CONTACT);
return true;
}
@@ -1777,9 +1813,53 @@ public class ContactsProvider2 extends AbstractContactsProvider
DeletedContactsTableUtil.deleteOldLogs(db);
break;
}
+
+ case BACKGROUND_TASK_ADD_DEFAULT_CONTACT: {
+ if (shouldAttemptPreloadingContacts()) {
+ try {
+ InputStream inputStream = getContext().getResources().openRawResource(
+ R.raw.preloaded_contacts);
+ PreloadedContactsFileParser pcfp = new
+ PreloadedContactsFileParser(inputStream);
+ ArrayList<ContentProviderOperation> cpOperations = pcfp.parseForContacts();
+ if (cpOperations == null) break;
+
+ getContext().getContentResolver().applyBatch(ContactsContract.AUTHORITY,
+ cpOperations);
+ // persist the completion of the transaction
+ onPreloadingContactsComplete();
+
+ } catch (NotFoundException nfe) {
+ System.out.println();
+ nfe.printStackTrace();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } catch (OperationApplicationException e) {
+ e.printStackTrace();
+ }
+ }
+
+ break;
+ }
+
}
}
+ private boolean shouldAttemptPreloadingContacts() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ return getContext().getResources().getBoolean(R.bool.config_preload_contacts) &&
+ !prefs.getBoolean(PREF_PRELOADED_CONTACTS_ADDED, false);
+ }
+
+ private void onPreloadingContactsComplete() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(PREF_PRELOADED_CONTACTS_ADDED, true);
+ editor.commit();
+ }
+
public void onLocaleChanged() {
if (mProviderStatus != STATUS_NORMAL
&& mProviderStatus != STATUS_NO_ACCOUNTS_NO_CONTACTS) {
@@ -5351,13 +5431,43 @@ public class ContactsProvider2 extends AbstractContactsProvider
filterParam = uri.getLastPathSegment();
}
- // If the query consists of a single word, we can do snippetizing after-the-fact for
- // a performance boost. Otherwise, we can't defer.
+ // If the query consists of a single word, we can do snippetizing
+ // after-the-fact for a performance boost. Otherwise, we can't defer.
snippetDeferred = isSingleWordQuery(filterParam)
&& deferredSnipRequested && snippetNeeded(projection);
setTablesAndProjectionMapForContactsWithSnippet(
qb, uri, projection, filterParam, directoryId,
snippetDeferred);
+ long groupId = -1;
+ try {
+ groupId = Long.parseLong(uri.getQueryParameter(Groups._ID));
+ } catch (Exception exception) {
+ groupId = -1;
+ }
+ if (groupId != -1) {
+ StringBuilder groupBuilder = new StringBuilder();
+ if (uri.getBooleanQueryParameter(ADD_GROUP_MEMBERS, false)) {
+ // filter all the contacts that are NOT assigned to the
+ // group whose id is 'groupId'
+ groupBuilder.append(Contacts._ID + " NOT IN (" + " SELECT DISTINCT "
+ + Data.CONTACT_ID
+ + " FROM " + Views.DATA + " WHERE " + Data.MIMETYPE + " = '"
+ + GroupMembership.CONTENT_ITEM_TYPE + "' AND " + Data.DATA1
+ + " = " + groupId + ")");
+ } else {
+ // filter all the contacts that are assigned to the
+ // group whose id is 'groupId'
+ groupBuilder.append(Contacts._ID + " IN (" + " SELECT DISTINCT "
+ + Data.CONTACT_ID
+ + " FROM " + Views.DATA + " WHERE " + Data.MIMETYPE + " = '"
+ + GroupMembership.CONTENT_ITEM_TYPE + "' AND " + Data.DATA1
+ + " = " + groupId + ")");
+ }
+ if (isWhereAppended) {
+ qb.appendWhere(" AND ");
+ }
+ qb.appendWhere(groupBuilder.toString());
+ }
break;
}
@@ -5539,7 +5649,23 @@ public class ContactsProvider2 extends AbstractContactsProvider
case CONTACTS_GROUP: {
setTablesAndProjectionMapForContacts(qb, projection);
- if (uri.getPathSegments().size() > 2) {
+ appendLocalDirectoryAndAccountSelectionIfNeeded(qb, directoryId, uri);
+ long groupId = -1;
+ try {
+ groupId = Long.parseLong(uri.getQueryParameter(Groups._ID));
+ } catch (Exception exception) {
+ groupId = -1;
+ }
+ if (groupId != -1) {
+ qb.appendWhere(" AND ");
+ if (uri.getBooleanQueryParameter(ADD_GROUP_MEMBERS, false)) {
+ qb.appendWhere(CONTACTS_NOT_IN_GROUP_ID_SELECT);
+ } else {
+ qb.appendWhere(CONTACTS_IN_GROUP_ID_SELECT);
+ }
+ selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(groupId));
+ } else if (uri.getPathSegments().size() > 2) {
+ qb.appendWhere(" AND ");
qb.appendWhere(CONTACTS_IN_GROUP_SELECT);
String groupMimeTypeId = String.valueOf(
mDbHelper.get().getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE));
@@ -7320,9 +7446,63 @@ public class ContactsProvider2 extends AbstractContactsProvider
private void setTablesAndProjectionMapForContactsWithSnippet(SQLiteQueryBuilder qb, Uri uri,
String[] projection, String filter, long directoryId, boolean deferSnippeting) {
+ isWhereAppended = false;
StringBuilder sb = new StringBuilder();
sb.append(Views.CONTACTS);
+ /* Do not show contacts when SIM card is disabled for CONTACTS_FILTER */
+ StringBuilder sbWhere = new StringBuilder();
+ String withoutSim = getQueryParameter(uri, WITHOUT_SIM_FLAG );
+ if ("true".equals(withoutSim)) {
+ final long[] accountId = getAccountIdWithoutSim(uri);
+ if (accountId == null) {
+ // No such account.
+ sbWhere.setLength(0);
+ sbWhere.append("(1=2)");
+ } else {
+ if (accountId.length > 0) {
+ sbWhere.append(" (" + Contacts._ID + " not IN (" + "SELECT "
+ + RawContacts.CONTACT_ID + " FROM "
+ + Tables.RAW_CONTACTS + " WHERE "
+ + RawContacts.CONTACT_ID + " not NULL AND ( ");
+ for (int i = 0; i < accountId.length; i++) {
+ sbWhere.append(RawContactsColumns.ACCOUNT_ID + "="
+ + accountId[i]);
+ if (i != accountId.length - 1) {
+ sbWhere.append(" OR ");
+ }
+ }
+ sbWhere.append(")))");
+ }
+ }
+ } else {
+ final AccountWithDataSet accountWithDataSet = getAccountWithDataSetFromUri(uri);
+ // Accounts are valid by only checking one parameter, since we've
+ // already ruled out partial accounts.
+ final boolean validAccount = !TextUtils.isEmpty(accountWithDataSet.getAccountName());
+ if (validAccount) {
+ final Long accountId = mDbHelper.get().getAccountIdOrNull(accountWithDataSet);
+ if (accountId != null) {
+ sbWhere.append(" INNER JOIN (SELECT "
+ + RawContacts.CONTACT_ID
+ + " AS raw_contact_contact_id FROM "
+ + Tables.RAW_CONTACTS + " WHERE "
+ + RawContactsColumns.ACCOUNT_ID + " = "
+ + accountId
+ + ") ON raw_contact_contact_id = " + Contacts._ID);
+ }
+ }
+ }
+
+ if (!TextUtils.isEmpty(sbWhere.toString())) {
+ if ("true".equals(withoutSim)) {
+ qb.appendWhere(sbWhere.toString());
+ isWhereAppended = true;
+ } else {
+ sb.append(sbWhere.toString());
+ }
+ }
+
if (filter != null) {
filter = filter.trim();
}
@@ -7359,13 +7539,230 @@ public class ContactsProvider2 extends AbstractContactsProvider
int maxTokens = args != null && args.length > 3 ? Integer.parseInt(args[3])
: DEFAULT_SNIPPET_ARG_MAX_TOKENS;
- appendSearchIndexJoin(
- sb, filter, true, startMatch, endMatch, ellipsis, maxTokens, deferSnippeting);
+ if (isPhoneNumberFuzzySearchEnabled) {
+ appendSearchIndexJoinForFuzzySearch(sb, filter, true,
+ startMatch, endMatch, ellipsis, maxTokens, deferSnippeting);
+ } else {
+ appendSearchIndexJoin(sb, filter, true, startMatch, endMatch,
+ ellipsis, maxTokens, deferSnippeting);
+ }
} else {
- appendSearchIndexJoin(sb, filter, false, null, null, null, 0, false);
+ if (isPhoneNumberFuzzySearchEnabled) {
+ appendSearchIndexJoinForFuzzySearch(sb, filter, false, null,
+ null, null, 0, false);
+ } else {
+ appendSearchIndexJoin(sb, filter, false, null, null, null, 0,
+ false);
+ }
}
}
+ public void appendSearchIndexJoinForFuzzySearch(StringBuilder sb, String filter,
+ boolean snippetNeeded, String startMatch, String endMatch, String ellipsis,
+ int maxTokens, boolean deferSnippeting) {
+ boolean isEmailAddress = false;
+ String emailAddress = null;
+ boolean isPhoneNumber = false;
+ String phoneNumber = null;
+ String numberE164 = null;
+
+
+ if (filter.indexOf('@') != -1) {
+ emailAddress = mDbHelper.get().extractAddressFromEmailAddress(filter);
+ isEmailAddress = !TextUtils.isEmpty(emailAddress);
+ } else {
+ isPhoneNumber = isPhoneNumber(filter);
+ if (isPhoneNumber) {
+ phoneNumber = PhoneNumberUtils.normalizeNumber(filter);
+ numberE164 = PhoneNumberUtils.formatNumberToE164(phoneNumber,
+ mDbHelper.get().getCurrentCountryIso());
+ }
+ }
+
+ final String SNIPPET_CONTACT_ID = "snippet_contact_id";
+ sb.append(" JOIN (SELECT " + SearchIndexColumns.CONTACT_ID + " AS " + SNIPPET_CONTACT_ID);
+ if (snippetNeeded) {
+ sb.append(", ");
+ if (isEmailAddress) {
+ sb.append("ifnull(");
+ if (!deferSnippeting) {
+ // Add the snippet marker only when we're really creating snippet.
+ DatabaseUtils.appendEscapedSQLString(sb, startMatch);
+ sb.append("||");
+ }
+ sb.append("(SELECT MIN(" + Email.ADDRESS + ")");
+ sb.append(" FROM " + Tables.DATA_JOIN_RAW_CONTACTS);
+ sb.append(" WHERE " + Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID);
+ sb.append("=" + RawContacts.CONTACT_ID + " AND " + Email.ADDRESS + " LIKE ");
+ DatabaseUtils.appendEscapedSQLString(sb, filter + "%");
+ sb.append(")");
+ if (!deferSnippeting) {
+ sb.append("||");
+ DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+ }
+ sb.append(",");
+
+ if (deferSnippeting) {
+ sb.append(SearchIndexColumns.CONTENT);
+ } else {
+ appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens);
+ }
+ sb.append(")");
+ } else if (isPhoneNumber) {
+ sb.append("ifnull(");
+ if (!deferSnippeting) {
+ // Add the snippet marker only when we're really creating snippet.
+ DatabaseUtils.appendEscapedSQLString(sb, startMatch);
+ sb.append("||");
+ }
+ sb.append("(SELECT MIN(" + Phone.NUMBER + ")");
+ sb.append(" FROM " +
+ Tables.DATA_JOIN_RAW_CONTACTS + " JOIN " + Tables.PHONE_LOOKUP);
+ sb.append(" ON " + DataColumns.CONCRETE_ID);
+ sb.append("=" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID);
+ sb.append(" WHERE " + Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID);
+ sb.append("=" + RawContacts.CONTACT_ID);
+ sb.append(" AND (" + PhoneLookupColumns.NORMALIZED_NUMBER + " LIKE '%");
+ sb.append(phoneNumber);
+ sb.append("%'");
+ sb.append("))");
+ if (! deferSnippeting) {
+ sb.append("||");
+ DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+ }
+ sb.append(",");
+
+ if (deferSnippeting) {
+ sb.append(SearchIndexColumns.CONTENT);
+ } else {
+ appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens);
+ }
+ sb.append(")");
+ } else {
+ final String normalizedFilter = NameNormalizer.normalize(filter);
+ if (!TextUtils.isEmpty(normalizedFilter)) {
+ if (deferSnippeting) {
+ sb.append(SearchIndexColumns.CONTENT);
+ } else {
+ sb.append("(CASE WHEN EXISTS (SELECT 1 FROM ");
+ sb.append(Tables.RAW_CONTACTS + " AS rc INNER JOIN ");
+ sb.append(Tables.NAME_LOOKUP + " AS nl ON (rc." + RawContacts._ID);
+ sb.append("=nl." + NameLookupColumns.RAW_CONTACT_ID);
+ sb.append(") WHERE nl." + NameLookupColumns.NORMALIZED_NAME);
+ sb.append(" GLOB '" + normalizedFilter + "*' AND ");
+ sb.append("nl." + NameLookupColumns.NAME_TYPE + "=");
+ sb.append(NameLookupType.NAME_COLLATION_KEY + " AND ");
+ sb.append(Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID);
+ sb.append("=rc." + RawContacts.CONTACT_ID);
+ sb.append(") THEN NULL ELSE ");
+ appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens);
+ sb.append(" END)");
+ }
+ } else {
+ sb.append("NULL");
+ }
+ }
+ sb.append(" AS " + SearchSnippets.SNIPPET);
+ }
+
+ sb.append(" FROM " + Tables.SEARCH_INDEX);
+ sb.append(" WHERE ");
+ if (isPhoneNumber) {
+ sb.append(Tables.SEARCH_INDEX + " MATCH '");
+ // normalized version of the phone number (phoneNumber can only have
+ // + and digits)
+ final String phoneNumberCriteria = " OR tokens:" + phoneNumber
+ + "*";
+
+ // international version of this number (numberE164 can only have +
+ // and digits)
+ final String numberE164Criteria = (numberE164 != null && !TextUtils
+ .equals(numberE164, phoneNumber)) ? " OR tokens:"
+ + numberE164 + "*" : "";
+
+ // combine all criteria
+ final String commonCriteria = phoneNumberCriteria
+ + numberE164Criteria;
+
+ // search in content
+ sb.append(SearchIndexManager.getFtsMatchQuery(filter,
+ FtsQueryBuilder.getDigitsQueryBuilder(commonCriteria)));
+ sb.append("' AND " + SNIPPET_CONTACT_ID + " IN "
+ + Tables.DEFAULT_DIRECTORY);
+ if (snippetNeeded) {
+ // only support fuzzy search when there is snippet column and
+ // the filter is phone number!
+ sb.append(" UNION SELECT " + SearchIndexColumns.CONTACT_ID + " AS " +
+ SNIPPET_CONTACT_ID);
+ sb.append(", ");
+ if (!deferSnippeting) {
+ // Add the snippet marker only when we're really creating snippet.
+ DatabaseUtils.appendEscapedSQLString(sb, startMatch);
+ sb.append("||");
+ }
+ sb.append("(SELECT MIN(" + Phone.NUMBER + ")");
+ sb.append(" FROM " +
+ Tables.DATA_JOIN_RAW_CONTACTS + " JOIN " + Tables.PHONE_LOOKUP);
+ sb.append(" ON " + DataColumns.CONCRETE_ID);
+ sb.append("=" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID);
+ sb.append(" WHERE " + Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID);
+ sb.append("=" + RawContacts.CONTACT_ID);
+ sb.append(" AND (" + PhoneLookupColumns.NORMALIZED_NUMBER + " LIKE '%");
+ sb.append(phoneNumber);
+ sb.append("%'");
+ sb.append("))");
+ if (!deferSnippeting) {
+ sb.append("||");
+ DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+ }
+ sb.append(" AS " + SearchSnippets.SNIPPET);
+ sb.append(" FROM " + Tables.SEARCH_INDEX);
+ sb.append(" WHERE " + SearchSnippets.SNIPPET + " IS NOT NULL ");
+ sb.append(" AND " + SNIPPET_CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY + ")");
+ } else {
+ sb.append(")");
+ }
+ } else {
+ sb.append(Tables.SEARCH_INDEX + " MATCH '");
+ if (isEmailAddress) {
+ // we know that the emailAddress contains a @. This phrase search should be
+ // scoped against "content:" only, but unfortunately SQLite doesn't support
+ // phrases and scoped columns at once. This is fine in this case however, because:
+ // We can't erroneously match against name, as it is all-hex (so the @ can't match)
+ // We can't match against tokens, because phone-numbers can't contain @
+ final String sanitizedEmailAddress =
+ emailAddress == null ? "" : sanitizeMatch(emailAddress);
+ sb.append("\"");
+ sb.append(sanitizedEmailAddress);
+ sb.append("*\"");
+ } else if (isPhoneNumber) {
+ // normalized version of the phone number (phoneNumber can only have + and digits)
+ final String phoneNumberCriteria = " OR tokens:" + phoneNumber + "*";
+
+ // international version of this number (numberE164 can only have + and digits)
+ final String numberE164Criteria =
+ (numberE164 != null && !TextUtils.equals(numberE164, phoneNumber))
+ ? " OR tokens:" + numberE164 + "*"
+ : "";
+
+ // combine all criteria
+ final String commonCriteria =
+ phoneNumberCriteria + numberE164Criteria;
+
+ // search in content
+ sb.append(SearchIndexManager.getFtsMatchQuery(filter,
+ FtsQueryBuilder.getDigitsQueryBuilder(commonCriteria)));
+ } else {
+ // general case: not a phone number, not an email-address
+ sb.append(SearchIndexManager.getFtsMatchQuery(filter,
+ FtsQueryBuilder.SCOPED_NAME_NORMALIZING));
+ }
+ // Omit results in "Other Contacts".
+ sb.append("' AND " + SNIPPET_CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY + ")");
+ }
+ sb.append(" ON (" + Contacts._ID + "=" + SNIPPET_CONTACT_ID + ")");
+ }
+
public void appendSearchIndexJoin(StringBuilder sb, String filter,
boolean snippetNeeded, String startMatch, String endMatch, String ellipsis,
int maxTokens, boolean deferSnippeting) {
@@ -7747,22 +8144,50 @@ public class ContactsProvider2 extends AbstractContactsProvider
sb.append("(1)");
}
- final AccountWithDataSet accountWithDataSet = getAccountWithDataSetFromUri(uri);
- // Accounts are valid by only checking one parameter, since we've
- // already ruled out partial accounts.
- final boolean validAccount = !TextUtils.isEmpty(accountWithDataSet.getAccountName());
- if (validAccount) {
- final Long accountId = mDbHelper.get().getAccountIdOrNull(accountWithDataSet);
+ String withoutSim = getQueryParameter(uri, WITHOUT_SIM_FLAG );
+ if ("true".equals(withoutSim)) {
+ final long[] accountId = getAccountIdWithoutSim(uri);
+
if (accountId == null) {
// No such account.
sb.setLength(0);
sb.append("(1=2)");
} else {
- sb.append(
- " AND (" + Contacts._ID + " IN (" +
- "SELECT " + RawContacts.CONTACT_ID + " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContactsColumns.ACCOUNT_ID + "=" + accountId.toString() +
- "))");
+ if (accountId.length > 0) {
+ sb.append(
+ " AND (" + Contacts._ID + " not IN (" +
+ "SELECT " + RawContacts.CONTACT_ID + " FROM "
+ + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + " not NULL AND ( ");
+ for (int i = 0; i < accountId.length; i++) {
+ sb.append(RawContactsColumns.ACCOUNT_ID + "="
+ + accountId[i]);
+ if (i != accountId.length - 1) {
+ sb.append(" or ");
+ }
+ }
+ sb.append(")))");
+ }
+ }
+ }
+ else {
+ final AccountWithDataSet accountWithDataSet = getAccountWithDataSetFromUri(uri);
+ // Accounts are valid by only checking one parameter, since we've
+ // already ruled out partial accounts.
+ final boolean validAccount = !TextUtils.isEmpty(accountWithDataSet.getAccountName());
+ if (validAccount) {
+ final Long accountId = mDbHelper.get().getAccountIdOrNull(accountWithDataSet);
+ if (accountId == null) {
+ // No such account.
+ sb.setLength(0);
+ sb.append("(1=2)");
+ } else {
+ sb.append(
+ " AND (" + Contacts._ID + " IN (" +
+ "SELECT " + RawContacts.CONTACT_ID + " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContactsColumns.ACCOUNT_ID + "=" + accountId.toString() +
+ "))");
+ }
}
}
qb.appendWhere(sb.toString());
@@ -7812,6 +8237,56 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
+ private long[] getAccountIdWithoutSim(Uri uri) {
+ final String accountType = getQueryParameter(uri, RawContacts.ACCOUNT_TYPE);
+ final String accountName = getQueryParameter(uri, RawContacts.ACCOUNT_NAME);
+ Cursor c = null;
+ SQLiteDatabase db = mContactsHelper.getWritableDatabase();
+ long[] accountId = null;
+ try {
+ if (null != accountType) {
+ c = db.query(Tables.ACCOUNTS,
+ new String[] { AccountsColumns._ID },
+ AccountsColumns.ACCOUNT_TYPE + "=?",
+ new String[] { String.valueOf(accountType) }, null,
+ null, null);
+ } else if (!TextUtils.isEmpty(accountName)) {
+ String[] names = accountName.split(",");
+ int nameCount = names.length;
+ String where = AccountsColumns.ACCOUNT_NAME + "=?";
+ StringBuilder selection = new StringBuilder();
+ String[] selectionArgs = new String[nameCount];
+ for (int i = 0; i < nameCount; i++) {
+ selection.append(where);
+ if (i != nameCount - 1) {
+ selection.append(" OR ");
+ }
+ selectionArgs[i] = names[i];
+ }
+ c = db.query(Tables.ACCOUNTS,
+ new String[] { AccountsColumns._ID },
+ selection.toString(),
+ selectionArgs, null,
+ null, null);
+ }
+
+ if (c != null) {
+ accountId = new long[c.getCount()];
+
+ for (int i = 0; i < c.getCount(); i++) {
+ if (c.moveToNext()) {
+ accountId[c.getPosition()] = c.getInt(0);
+ }
+ }
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return accountId;
+ }
+
private AccountWithDataSet getAccountWithDataSetFromUri(Uri uri) {
final String accountName = getQueryParameter(uri, RawContacts.ACCOUNT_NAME);
final String accountType = getQueryParameter(uri, RawContacts.ACCOUNT_TYPE);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
index 1de0823..42b4884 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
@@ -17,6 +17,11 @@ package com.android.providers.contacts;
import android.content.Context;
import com.android.providers.contacts.aggregation.AbstractContactAggregator;
+import android.content.ContentValues;
+import android.provider.ContactsContract.Data;
+
+import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
public class DataRowHandlerForCustomMimetype extends DataRowHandler {
@@ -24,4 +29,17 @@ public class DataRowHandlerForCustomMimetype extends DataRowHandler {
AbstractContactAggregator aggregator, String mimetype) {
super(context, dbHelper, aggregator, mimetype);
}
+ @Override
+ public boolean hasSearchableData() {
+ return true;
+ }
+ @Override
+ public boolean containsSearchableColumns(ContentValues values) {
+ return values.containsKey(Data.DATA1);
+ }
+
+ @Override
+ public void appendSearchableData(IndexBuilder builder) {
+ builder.appendContentFromColumn(Data.DATA1);
+ }
}
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index 598a4a0..060f87e 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -1937,8 +1937,7 @@ public class LegacyApiSupport {
sb.append(" AND " + RawContacts.ACCOUNT_TYPE + "=");
DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
} else {
- sb.append(RawContacts.ACCOUNT_NAME + " IS NULL" +
- " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL");
+ sb.append("1=1");
}
}
@@ -1955,8 +1954,10 @@ public class LegacyApiSupport {
sb.append(" AND " + Groups.ACCOUNT_TYPE + "=");
DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
} else {
- sb.append(Groups.ACCOUNT_NAME + " IS NULL" +
- " AND " + Groups.ACCOUNT_TYPE + " IS NULL");
+ sb.append(Groups.ACCOUNT_NAME + " = '"
+ + AccountWithDataSet.LOCAL.getAccountName() + "' AND "
+ + Groups.ACCOUNT_TYPE + " = '"
+ + AccountWithDataSet.LOCAL.getAccountType() + "'");
}
}
diff --git a/src/com/android/providers/contacts/util/PreloadedContactsFileParser.java b/src/com/android/providers/contacts/util/PreloadedContactsFileParser.java
new file mode 100644
index 0000000..ad9d0c0
--- /dev/null
+++ b/src/com/android/providers/contacts/util/PreloadedContactsFileParser.java
@@ -0,0 +1,225 @@
+package com.android.providers.contacts.util;
+
+import android.content.ContentProviderOperation;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.providers.contacts.Constants;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Responsible for parsing the preloaded_contacts.json file and helping generate database commands
+ * to persist that information.
+ *
+ * Details about the json schema and encoding specification for the properties can be found in
+ * the preloaded_contacts_schema.json file under 'res/raw'
+ */
+public class PreloadedContactsFileParser {
+
+ private static final String TAG = "PreloadContacts";
+
+ private static String TOKEN_AT = "@";
+ private static String TOKEN_AT_SUB = "android.provider.ContactsContract$CommonDataKinds";
+ private static String TOKEN_CONTACTS_ROOT = "contacts";
+ private static String TOKEN_CONTACT_DATA = "data";
+ private static String TOKEN_MIMETYPE = "@mimetype";
+ private static String TOKEN_MIMETYPE_SUB = "android.provider.ContactsContract$Data.MIMETYPE";
+
+ private static Character CLASS_NAME_DELIMITER = '.';
+
+ private static Pattern mExpressionPattern = Pattern.compile("\\{\\{(.+?)\\}\\}");
+
+ private boolean mDebug;
+ private JSONObject mJsonRoot;
+ private HashMap<String,String> mResolvedNameCache;
+
+ public PreloadedContactsFileParser(InputStream inputStream) throws JSONException {
+ mDebug = Log.isLoggable(Constants.TAG_DEBUG_PRELOAD_CONTACTS, Log.DEBUG);
+ String jsonString = convertInputStreamToString(inputStream);
+ mJsonRoot = new JSONObject(jsonString);
+ mResolvedNameCache = new HashMap<String, String>();
+ }
+
+ private String convertInputStreamToString(InputStream is) {
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ StringBuilder sb = new StringBuilder();
+
+ String line = null;
+ try {
+ while ((line = br.readLine()) != null) {
+ sb.append(line).append('\n');
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Parses the json object and creates the necessary {@link ContentProviderOperation}s to
+ * construct the contacts specified
+ */
+ public ArrayList<ContentProviderOperation> parseForContacts() {
+ try {
+ ArrayList<ContentProviderOperation> cpOps = new ArrayList<ContentProviderOperation>();
+ JSONArray contacts = mJsonRoot.getJSONArray(TOKEN_CONTACTS_ROOT);
+ int numContacts = contacts.length();
+
+ for (int i = 0; i < numContacts; ++i) {
+ JSONArray contactData = contacts.getJSONObject(i).getJSONArray(TOKEN_CONTACT_DATA);
+ int rawEntries = contactData.length();
+
+ // create a new raw contact entry
+ cpOps.add(
+ ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
+ .build() );
+ int cvBackRef = cpOps.size() - 1;
+
+ for (int j = 0; j < rawEntries; ++j) {
+ JSONObject rawEntry = contactData.getJSONObject(j);
+ Iterator<String> keys = rawEntry.keys();
+
+ // build a ContentProviderOperation to add the contact's raw entry
+ ContentProviderOperation.Builder cpoBuilder =
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+ cpoBuilder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
+ cvBackRef);
+
+ while (keys.hasNext()) {
+ String key = keys.next();
+ String value = rawEntry.getString(key);
+
+ if (mDebug) {
+ Log.d(TAG, "parsing property : " + key);
+ Log.d(TAG, "parsing property value : " + value);
+ }
+
+ String resolvedKey = null;
+ // keys always need interpolation
+ resolvedKey = resolvePropertyName(key);
+ // determine if the property is an expression that need to be evaluated
+ String resolvedValue = value;
+ Matcher matcher = mExpressionPattern.matcher(value);
+ if (matcher.matches()) {
+ matcher.reset();
+ matcher.find();
+ resolvedValue = resolvePropertyName(matcher.group(1));
+ }
+
+ if (mDebug) {
+ Log.d(TAG, "resolved property name : " + resolvedKey);
+ Log.d(TAG, "resolved property value : " + resolvedValue);
+ }
+
+ if (TextUtils.isEmpty(resolvedKey) || TextUtils.isEmpty(resolvedValue)) {
+ // don't persist this raw_contact value
+ continue;
+ } else {
+ cpoBuilder.withValue(resolvedKey, resolvedValue);
+ }
+
+ }
+
+ cpOps.add(cpoBuilder.build());
+ }
+
+ }
+ return cpOps;
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * parses an object's property name to determine its codified value
+ */
+ private String resolvePropertyName(String encodedName) {
+ if (TextUtils.isEmpty(encodedName)) {
+ return null;
+ }
+
+ if (mResolvedNameCache.containsKey(encodedName)) {
+ return mResolvedNameCache.get(encodedName);
+ }
+
+ String unwrappedName = encodedName;
+ // check if any substitution rules apply
+ if (TextUtils.equals(TOKEN_MIMETYPE, encodedName)) {
+ unwrappedName = TOKEN_MIMETYPE_SUB;
+ } else if (encodedName.startsWith(TOKEN_AT)) {
+ unwrappedName = encodedName.replace(TOKEN_AT, TOKEN_AT_SUB);
+ }
+
+ if (mDebug) {
+ Log.d(TAG, "encoded property name : " + encodedName);
+ Log.d(TAG, "resolved property name : " + unwrappedName);
+ }
+
+ String resolvedName = resolveCodifiedName(unwrappedName);
+ mResolvedNameCache.put(encodedName, resolvedName);
+ return resolvedName;
+ }
+
+ /**
+ * returns the string-ified value of the Java field the property name points to
+ */
+ private String resolveCodifiedName(String absoluteName) {
+ int delimiterIndex = TextUtils.lastIndexOf(absoluteName, CLASS_NAME_DELIMITER);
+ // ensure there is a field identifier to read
+ if (delimiterIndex == -1 || delimiterIndex >= absoluteName.length() - 1) {
+ return null;
+ }
+
+ String className = TextUtils.substring(absoluteName, 0, delimiterIndex);
+ String fieldName = absoluteName.substring(delimiterIndex + 1);
+
+ if (mDebug) {
+ Log.d(TAG, "property's class : " + className);
+ Log.d(TAG, "property's field : " + fieldName);
+ }
+
+ try {
+ Class clazz = Class.forName(className);
+ Field field = clazz.getField(fieldName);
+ String fieldValue = field.get(clazz).toString();
+ if (mDebug) {
+ Log.d(TAG, "fully resolved property name : " + fieldValue);
+ }
+ return fieldValue;
+
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+}