am 0a940aac: Revert "Update list of unrestricted packages"
diff --git a/Android.mk b/Android.mk
index 0224435..bcd6b9e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -10,7 +10,14 @@
LOCAL_JAVA_LIBRARIES := ext
-LOCAL_STATIC_JAVA_LIBRARIES += android-common
+LOCAL_STATIC_JAVA_LIBRARIES += android-common com.android.vcard
+
+# The Emma tool analyzes code coverage when running unit tests on the
+# application. This configuration line selects which packages will be analyzed,
+# leaving out code which is tested by other means (e.g. static libraries) that
+# would dilute the coverage results. These options do not affect regular
+# production builds.
+LOCAL_EMMA_COVERAGE_FILTER := +com.android.providers.contacts.*
# The Emma tool analyzes code coverage when running unit tests on the
# application. This configuration line selects which packages will be analyzed,
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f56fac4..d36dd0c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -12,6 +12,8 @@
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.cp" />
<uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ" />
<uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application android:process="android.process.acore"
android:label="@string/app_label"
@@ -59,5 +61,30 @@
<action android:name="android.intent.action.PRE_BOOT_COMPLETED"/>
</intent-filter>
</receiver>
+
+ <receiver android:name="PackageIntentReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_ADDED" />
+ <data android:scheme="package" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_REPLACED" />
+ <data android:scheme="package" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_REMOVED" />
+ <data android:scheme="package" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_CHANGED" />
+ <data android:scheme="package" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="LocaleChangeReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.LOCALE_CHANGED"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index fe20126..285513e 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Upgrade kontaktu vyžaduje více paměti"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Nedostatek paměti pro upgrade kontaktu"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Výběrem této možnosti dokončíte upgrade."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontakty"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Jiné"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 06d13e3..54311f9 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Opgradering af kontaktpersoner kræver mere hukommelse"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Opgraderer lagerplads til kontaktpersoner"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Vælg for at fuldføre opgraderingen."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontaktpersoner"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Andre"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 005eb0d..749d9fd 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Kontaktupgrade erfordert mehr Speicher"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Kontaktspeicher wird aktualisiert"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Zum Abschluss des Upgrades auswählen"</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontakte"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Andere"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index f1cc0f8..e6b59b6 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Η αναβάθμιση των επαφών απαιτεί περισσότερη μνήμη"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Αναβάθμιση χώρου αποθήκευσης επαφών"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Επιλέξτε για την ολοκλήρωση της αναβάθμισης."</string>
+ <string name="default_directory" msgid="93961630309570294">"Επαφές"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Άλλο"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 73225e5..af8742b 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"La actualización de los contactos necesita más memoria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Actualizando almacenamiento de contactos"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Selecciona para completar la actualización."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contactos"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Otro"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index eecb1e1..730a3e4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"La actualización del contacto necesita más memoria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Espacio de almacenamiento de actualización de contactos"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Selecciona esta opción para completar la actualización."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contactos"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Otro"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 7c669b0..6cef179 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"La mise à niveau des contacts requiert plus de mémoire."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Mise à niveau du stockage des contacts"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Sélectionnez pour effectuer la mise à niveau."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contacts"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Autre"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index bd749d4..f02e6b6 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"L\'aggiornamento dei contatti richiede più memoria"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Aggiornamento dell\'archivio contatti"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Seleziona per completare l\'aggiornamento."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contatti"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Altro"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 7913b36..38c7d0c 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"連絡先のアップグレードに必要なメモリが不足しています"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"連絡先ストレージのアップグレード"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"アップグレードを完了するには選択してください。"</string>
+ <string name="default_directory" msgid="93961630309570294">"連絡先"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"その他"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 140a996..9927a28 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"연락처를 업그레이드하려면 메모리가 더 필요합니다."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"연락처 저장소 업그레이드"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"업그레이드를 완료하려면 선택하세요."</string>
+ <string name="default_directory" msgid="93961630309570294">"주소록"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"기타"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 327f8d3..b306fee 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Det er ikke nok minne for å oppgradere kontaktene"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Oppgraderer lagrede kontakter"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Velg for å fullføre oppgraderingen."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontakter"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Andre"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index ccfc34e..859f194 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Voor het bijwerken van contacten is meer geheugen nodig"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Contactopslag bijwerken"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Selecteer om de upgrade te voltooien."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contacten"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Overig"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index f3b4122..c645d4b 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Uaktualnienie kontaktów wymaga więcej pamięci"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Uaktualnianie magazynu kontaktów"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Wybierz, aby dokończyć uaktualnianie."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontakty"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Inne"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 08bcae1..5c6c445 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"A actualização de contactos necessita de mais memória"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"A actualizar armazenamento de contactos"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Seleccione para concluir a actualização."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contactos"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Outro"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 3186b64..20cbef2 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"A atualização de contatos precisa de mais memória"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Armazenamento de atualização de contatos"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Selecione para concluir a atualização."</string>
+ <string name="default_directory" msgid="93961630309570294">"Contatos"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Outros"</string>
</resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
new file mode 100644
index 0000000..057fe15
--- /dev/null
+++ b/res/values-rm/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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">
+ <string name="app_label" msgid="3389954322874982620">"Glista da contacts"</string>
+ <string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
+ <string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Ina actualisaziun da contacts basegna dapli memoria"</string>
+ <string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Actualisar la memoria da contacts"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Tscherner per cumplettar l\'actualisaziun"</string>
+ <!-- no translation found for default_directory (93961630309570294) -->
+ <skip />
+ <!-- no translation found for local_invisible_directory (705244318477396120) -->
+ <skip />
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 6351180..c7353f6 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Необходимо больше памяти для обновления контактов"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Обновление памяти контактов"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Нажмите, чтобы завершить обновление."</string>
+ <string name="default_directory" msgid="93961630309570294">"Контакты"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Другое"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index d7a6707..bacb6b8 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Kontaktuppgradering kräver mer minne"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Uppgradera kontaktminne"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Välj för att slutföra uppgraderingen."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontakter"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Övrigt"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 92cea75..2cc4330 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Kişiyi yeni sürüme geçirmek için daha fazla bellek gerekiyor"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Kişi deposu yükseltiliyor"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Yeni sürüme geçmeyi tamamlamak için seçin."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kişiler"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Diğer"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 17a6003..4471b68 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"联系人升级需要更多的存储空间"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"升级联系人时存储空间不足"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"请选择以完成升级。"</string>
+ <string name="default_directory" msgid="93961630309570294">"联系人"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"其他"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 9da7cb5..4a2a4d8 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -21,4 +21,6 @@
<string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"需要更多記憶體才能將聯絡人升級"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"升級聯絡人儲存空間"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"選取以完成升級。"</string>
+ <string name="default_directory" msgid="93961630309570294">"聯絡人"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"其他"</string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ba8a7de..0e39d61 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -31,5 +31,11 @@
<!-- Text for the notification shown when updating contacts fails because of memory shortage -->
<string name="upgrade_out_of_memory_notification_text">Select to complete the upgrade.</string>
+
+ <!-- The name of the default contact directory -->
+ <string name="default_directory">Contacts</string>
+
+ <!-- The name of the invisible local contact directory -->
+ <string name="local_invisible_directory">Other</string>
</resources>
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 317e4a8..a574bee 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -65,11 +65,13 @@
sCallsProjectionMap.put(Calls.CACHED_NAME, Calls.CACHED_NAME);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_TYPE);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_LABEL, Calls.CACHED_NUMBER_LABEL);
+ sCallsProjectionMap.put(Calls.COUNTRY_ISO, Calls.COUNTRY_ISO);
}
private ContactsDatabaseHelper mDbHelper;
private DatabaseUtils.InsertHelper mCallsInserter;
private boolean mUseStrictPhoneNumberComparation;
+ private CountryMonitor mCountryMonitor;
@Override
public boolean onCreate() {
@@ -82,7 +84,7 @@
mUseStrictPhoneNumberComparation =
context.getResources().getBoolean(
com.android.internal.R.bool.config_use_strict_phone_number_comparation);
-
+ mCountryMonitor = CountryMonitor.getInstance(context);
return true;
}
@@ -150,6 +152,9 @@
@Override
public Uri insert(Uri uri, ContentValues values) {
+ // Inserted the current country code, so we know the country
+ // the number belongs to.
+ values.put(Calls.COUNTRY_ISO, getCurrentCountryIso());
long rowId = mCallsInserter.insert(values);
if (rowId > 0) {
notifyChange();
@@ -206,4 +211,8 @@
getContext().getContentResolver().notifyChange(CallLog.CONTENT_URI, null,
false /* wake up sync adapters */);
}
+
+ protected String getCurrentCountryIso() {
+ return mCountryMonitor.getCountryIso();
+ }
}
diff --git a/src/com/android/providers/contacts/ContactAggregator.java b/src/com/android/providers/contacts/ContactAggregator.java
index 314ed0a..019f9f1 100644
--- a/src/com/android/providers/contacts/ContactAggregator.java
+++ b/src/com/android/providers/contacts/ContactAggregator.java
@@ -35,14 +35,16 @@
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.FullNameStyle;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -93,6 +95,9 @@
private final ContactsProvider2 mContactsProvider;
private final ContactsDatabaseHelper mDbHelper;
private PhotoPriorityResolver mPhotoPriorityResolver;
+ private final NameSplitter mNameSplitter;
+ private final CommonNicknameCache mCommonNicknameCache;
+
private boolean mEnabled = true;
/** Precompiled sql statement for setting an aggregated presence */
@@ -130,6 +135,19 @@
private DisplayNameCandidate mDisplayNameCandidate = new DisplayNameCandidate();
/**
+ * Parameter for the suggestion lookup query.
+ */
+ public static final class AggregationSuggestionParameter {
+ public final String kind;
+ public final String value;
+
+ public AggregationSuggestionParameter(String kind, String value) {
+ this.kind = kind;
+ this.value = value;
+ }
+ }
+
+ /**
* Captures a potential match for a given name. The matching algorithm
* constructs a bunch of NameMatchCandidate objects for various potential matches
* and then executes the search in bulk.
@@ -169,6 +187,10 @@
public void clear() {
mCount = 0;
}
+
+ public boolean isEmpty() {
+ return mCount == 0;
+ }
}
/**
@@ -200,10 +222,13 @@
*/
public ContactAggregator(ContactsProvider2 contactsProvider,
ContactsDatabaseHelper contactsDatabaseHelper,
- PhotoPriorityResolver photoPriorityResolver) {
+ PhotoPriorityResolver photoPriorityResolver, NameSplitter nameSplitter,
+ CommonNicknameCache commonNicknameCache) {
mContactsProvider = contactsProvider;
mDbHelper = contactsDatabaseHelper;
mPhotoPriorityResolver = photoPriorityResolver;
+ mNameSplitter = nameSplitter;
+ mCommonNicknameCache = commonNicknameCache;
SQLiteDatabase db = mDbHelper.getReadableDatabase();
@@ -212,17 +237,17 @@
final String replaceAggregatePresenceSql =
"INSERT OR REPLACE INTO " + Tables.AGGREGATED_PRESENCE + "("
+ AggregatedPresenceColumns.CONTACT_ID + ", "
- + StatusUpdates.PRESENCE_STATUS + ", "
+ + StatusUpdates.PRESENCE + ", "
+ StatusUpdates.CHAT_CAPABILITY + ")"
+ " SELECT " + PresenceColumns.CONTACT_ID + ","
- + StatusUpdates.PRESENCE_STATUS + ","
+ + StatusUpdates.PRESENCE + ","
+ StatusUpdates.CHAT_CAPABILITY
+ " FROM " + Tables.PRESENCE
+ " WHERE "
- + " (" + StatusUpdates.PRESENCE_STATUS
+ + " (" + StatusUpdates.PRESENCE
+ " * 10 + " + StatusUpdates.CHAT_CAPABILITY + ")"
+ " = (SELECT "
- + "MAX (" + StatusUpdates.PRESENCE_STATUS
+ + "MAX (" + StatusUpdates.PRESENCE
+ " * 10 + " + StatusUpdates.CHAT_CAPABILITY + ")"
+ " FROM " + Tables.PRESENCE
+ " WHERE " + PresenceColumns.CONTACT_ID
@@ -997,6 +1022,9 @@
int NAME_TYPE_B = 3;
}
+ /**
+ * Finds contacts with names matching the name of the specified raw contact.
+ */
private void updateMatchScoresBasedOnNameMatches(SQLiteDatabase db, long rawContactId,
ContactMatcher matcher) {
mSelectionArgs1[0] = String.valueOf(rawContactId);
@@ -1021,6 +1049,101 @@
}
}
+ private interface NameLookupMatchQueryWithParameter {
+ String TABLE = Tables.NAME_LOOKUP
+ + " JOIN " + Tables.RAW_CONTACTS +
+ " ON (" + NameLookupColumns.RAW_CONTACT_ID + " = "
+ + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
+
+ String[] COLUMNS = new String[] {
+ RawContacts.CONTACT_ID,
+ NameLookupColumns.NORMALIZED_NAME,
+ NameLookupColumns.NAME_TYPE,
+ };
+
+ int CONTACT_ID = 0;
+ int NAME = 1;
+ int NAME_TYPE = 2;
+ }
+
+ private final class NameLookupSelectionBuilder extends NameLookupBuilder {
+
+ private final MatchCandidateList mCandidates;
+
+ private StringBuilder mSelection = new StringBuilder(
+ NameLookupColumns.NORMALIZED_NAME + " IN(");
+
+
+ public NameLookupSelectionBuilder(NameSplitter splitter, MatchCandidateList candidates) {
+ super(splitter);
+ this.mCandidates = candidates;
+ }
+
+ @Override
+ protected String[] getCommonNicknameClusters(String normalizedName) {
+ return mCommonNicknameCache.getCommonNicknameClusters(normalizedName);
+ }
+
+ @Override
+ protected void insertNameLookup(
+ long rawContactId, long dataId, int lookupType, String string) {
+ mCandidates.add(string, lookupType);
+ DatabaseUtils.appendEscapedSQLString(mSelection, string);
+ mSelection.append(',');
+ }
+
+ public boolean isEmpty() {
+ return mCandidates.isEmpty();
+ }
+
+ public String getSelection() {
+ mSelection.setLength(mSelection.length() - 1); // Strip last comma
+ mSelection.append(')');
+ return mSelection.toString();
+ }
+
+ public int getLookupType(String name) {
+ for (int i = 0; i < mCandidates.mCount; i++) {
+ if (mCandidates.mList.get(i).mName.equals(name)) {
+ return mCandidates.mList.get(i).mLookupType;
+ }
+ }
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Finds contacts with names matching the specified name.
+ */
+ private void updateMatchScoresBasedOnNameMatches(SQLiteDatabase db, String query,
+ MatchCandidateList candidates, ContactMatcher matcher) {
+ NameLookupSelectionBuilder builder = new NameLookupSelectionBuilder(
+ mNameSplitter, candidates);
+ builder.insertNameLookup(0, 0, query, FullNameStyle.UNDEFINED);
+ if (builder.isEmpty()) {
+ return;
+ }
+
+ Cursor c = db.query(NameLookupMatchQueryWithParameter.TABLE,
+ NameLookupMatchQueryWithParameter.COLUMNS, builder.getSelection(), null, null, null,
+ null, PRIMARY_HIT_LIMIT_STRING);
+ try {
+ while (c.moveToNext()) {
+ long contactId = c.getLong(NameLookupMatchQueryWithParameter.CONTACT_ID);
+ String name = c.getString(NameLookupMatchQueryWithParameter.NAME);
+ int nameTypeA = builder.getLookupType(name);
+ int nameTypeB = c.getInt(NameLookupMatchQueryWithParameter.NAME_TYPE);
+ matcher.matchName(contactId, nameTypeA, name, nameTypeB, name,
+ ContactMatcher.MATCHING_ALGORITHM_EXACT);
+ if (nameTypeA == NameLookupType.NICKNAME && nameTypeB == NameLookupType.NICKNAME) {
+ matcher.updateScoreWithNicknameMatch(contactId);
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
private interface EmailLookupQuery {
String TABLE = Tables.DATA + " dataA"
+ " JOIN " + Tables.DATA + " dataB" +
@@ -1251,9 +1374,8 @@
+ Contacts.STARRED + ", "
+ Contacts.HAS_PHONE_NUMBER + ", "
+ ContactsColumns.SINGLE_IS_RESTRICTED + ", "
- + Contacts.LOOKUP_KEY + ", "
- + Contacts.IN_VISIBLE_GROUP + ") " +
- " VALUES (?,?,?,?,?,?,?,?,?,?,0)";
+ + Contacts.LOOKUP_KEY + ") " +
+ " VALUES (?,?,?,?,?,?,?,?,?,?)";
int NAME_RAW_CONTACT_ID = 1;
int PHOTO_ID = 2;
@@ -1671,10 +1793,11 @@
* Finds matching contacts and returns a cursor on those.
*/
public Cursor queryAggregationSuggestions(SQLiteQueryBuilder qb, String[] projection,
- long contactId, int maxSuggestions, String filter) {
+ long contactId, int maxSuggestions, String filter,
+ ArrayList<AggregationSuggestionParameter> parameters) {
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
- List<MatchScore> bestMatches = findMatchingContacts(db, contactId);
+ List<MatchScore> bestMatches = findMatchingContacts(db, contactId, parameters);
return queryMatchingContacts(qb, db, contactId, projection, bestMatches, maxSuggestions,
filter);
}
@@ -1784,8 +1907,10 @@
/**
* Finds contacts with data matches and returns a list of {@link MatchScore}'s in the
* descending order of match score.
+ * @param parameters
*/
- private List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId) {
+ private List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId,
+ ArrayList<AggregationSuggestionParameter> parameters) {
MatchCandidateList candidates = new MatchCandidateList();
ContactMatcher matcher = new ContactMatcher();
@@ -1793,16 +1918,21 @@
// Don't aggregate a contact with itself
matcher.keepOut(contactId);
- final Cursor c = db.query(RawContactIdQuery.TABLE, RawContactIdQuery.COLUMNS,
- RawContacts.CONTACT_ID + "=" + contactId, null, null, null, null);
- try {
- while (c.moveToNext()) {
- long rawContactId = c.getLong(RawContactIdQuery._ID);
- updateMatchScoresForSuggestionsBasedOnDataMatches(db, rawContactId, candidates,
- matcher);
+ if (parameters == null || parameters.size() == 0) {
+ final Cursor c = db.query(RawContactIdQuery.TABLE, RawContactIdQuery.COLUMNS,
+ RawContacts.CONTACT_ID + "=" + contactId, null, null, null, null);
+ try {
+ while (c.moveToNext()) {
+ long rawContactId = c.getLong(RawContactIdQuery._ID);
+ updateMatchScoresForSuggestionsBasedOnDataMatches(db, rawContactId, candidates,
+ matcher);
+ }
+ } finally {
+ c.close();
}
- } finally {
- c.close();
+ } else {
+ updateMatchScoresForSuggestionsBasedOnDataMatches(db, candidates,
+ matcher, parameters);
}
return matcher.pickBestMatches(ContactMatcher.SCORE_THRESHOLD_SUGGEST);
@@ -1820,4 +1950,16 @@
loadNameMatchCandidates(db, rawContactId, candidates, false);
lookupApproximateNameMatches(db, candidates, matcher);
}
+
+ private void updateMatchScoresForSuggestionsBasedOnDataMatches(SQLiteDatabase db,
+ MatchCandidateList candidates, ContactMatcher matcher,
+ ArrayList<AggregationSuggestionParameter> parameters) {
+ for (AggregationSuggestionParameter parameter : parameters) {
+ if (AggregationSuggestions.PARAMETER_MATCH_NAME.equals(parameter.kind)) {
+ updateMatchScoresBasedOnNameMatches(db, parameter.value, candidates, matcher);
+ }
+
+ // TODO: add support for other parameter kinds
+ }
+ }
}
diff --git a/src/com/android/providers/contacts/ContactDirectoryManager.java b/src/com/android/providers/contacts/ContactDirectoryManager.java
new file mode 100644
index 0000000..51ad4a4
--- /dev/null
+++ b/src/com/android/providers/contacts/ContactDirectoryManager.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts;
+
+import com.android.providers.contacts.ContactsDatabaseHelper.DirectoryColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Directory;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the contents of the {@link Directory} table.
+ */
+public class ContactDirectoryManager extends HandlerThread {
+
+ private static final String TAG = "ContactDirectoryManager";
+
+ private static final int MESSAGE_SCAN_ALL_PROVIDERS = 0;
+ private static final int MESSAGE_SCAN_PACKAGES_BY_UID = 1;
+
+ public static final String PROPERTY_DIRECTORY_SCAN_COMPLETE = "directoryScanComplete";
+ public static final String CONTACT_DIRECTORY_META_DATA = "android.content.ContactDirectory";
+
+ public class DirectoryInfo {
+ long id;
+ String packageName;
+ String authority;
+ String accountName;
+ String accountType;
+ String displayName;
+ int typeResourceId;
+ int exportSupport = Directory.EXPORT_SUPPORT_NONE;
+ int shortcutSupport = Directory.SHORTCUT_SUPPORT_NONE;
+ int photoSupport = Directory.PHOTO_SUPPORT_NONE;
+ }
+
+ private final static class DirectoryQuery {
+ public static final String[] PROJECTION = {
+ Directory.ACCOUNT_NAME,
+ Directory.ACCOUNT_TYPE,
+ Directory.DISPLAY_NAME,
+ Directory.TYPE_RESOURCE_ID,
+ Directory.EXPORT_SUPPORT,
+ Directory.SHORTCUT_SUPPORT,
+ Directory.PHOTO_SUPPORT,
+ };
+
+ public static final int ACCOUNT_NAME = 0;
+ public static final int ACCOUNT_TYPE = 1;
+ public static final int DISPLAY_NAME = 2;
+ public static final int TYPE_RESOURCE_ID = 3;
+ public static final int EXPORT_SUPPORT = 4;
+ public static final int SHORTCUT_SUPPORT = 5;
+ public static final int PHOTO_SUPPORT = 6;
+ }
+
+ private final ContactsProvider2 mContactsProvider;
+ private Context mContext;
+ private Handler mHandler;
+
+ public ContactDirectoryManager(ContactsProvider2 contactsProvider) {
+ super("DirectoryManager", Process.THREAD_PRIORITY_BACKGROUND);
+ this.mContactsProvider = contactsProvider;
+ this.mContext = contactsProvider.getContext();
+ }
+
+ public ContactsDatabaseHelper getDbHelper() {
+ return (ContactsDatabaseHelper) mContactsProvider.getDatabaseHelper();
+ }
+
+ /**
+ * Launches an asynchronous scan of all packages.
+ */
+ @Override
+ public void start() {
+ super.start();
+ if (areTypeResourceIdsValid()) {
+ scheduleScanAllPackages(false);
+ } else {
+ getDbHelper().setProperty(PROPERTY_DIRECTORY_SCAN_COMPLETE, "0");
+ scanAllPackagesIfNeeded();
+ }
+ }
+ /**
+ * Launches an asynchronous scan of all packages owned by the current calling UID.
+ */
+ public void scheduleDirectoryUpdateForCaller() {
+ final int callingUid = Binder.getCallingUid();
+ if (isAlive()) {
+ Handler handler = getHandler();
+ handler.sendMessage(handler.obtainMessage(MESSAGE_SCAN_PACKAGES_BY_UID, callingUid, 0));
+ } else {
+ scanPackagesByUid(callingUid);
+ }
+ }
+
+ protected Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ ContactDirectoryManager.this.handleMessage(msg);
+ }
+ };
+ }
+ return mHandler;
+ }
+
+ protected void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MESSAGE_SCAN_ALL_PROVIDERS:
+ scanAllPackagesIfNeeded();
+ break;
+ case MESSAGE_SCAN_PACKAGES_BY_UID:
+ scanPackagesByUid(msg.arg1);
+ break;
+ }
+ }
+
+ /**
+ * Scans all packages owned by the specified calling UID looking for contact
+ * directory providers.
+ */
+ public void scanPackagesByUid(int callingUid) {
+ final PackageManager pm = mContext.getPackageManager();
+ final String[] callerPackages = pm.getPackagesForUid(callingUid);
+ if (callerPackages != null) {
+ for (int i = 0; i < callerPackages.length; i++) {
+ onPackageChanged(callerPackages[i]);
+ }
+ }
+ }
+
+ /**
+ * Scans through existing directories to see if the cached resource IDs still
+ * match their original resource names. If not - plays it safe by refreshing all directories.
+ *
+ * @return true if all resource IDs were found valid
+ */
+ private boolean areTypeResourceIdsValid() {
+ final PackageManager pm = mContext.getPackageManager();
+ SQLiteDatabase db = getDbHelper().getReadableDatabase();
+
+ Cursor cursor = db.query(Tables.DIRECTORIES,
+ new String[] { Directory.TYPE_RESOURCE_ID, Directory.PACKAGE_NAME,
+ DirectoryColumns.TYPE_RESOURCE_NAME }, null, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ int resourceId = cursor.getInt(0);
+ if (resourceId != 0) {
+ String packageName = cursor.getString(1);
+ String storedResourceName = cursor.getString(2);
+ String resourceName = getResourceNameById(pm, packageName, resourceId);
+ if (!TextUtils.equals(storedResourceName, resourceName)) {
+ return false;
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return true;
+ }
+
+ /**
+ * Given a resource ID, returns the corresponding resource name or null if the package name /
+ * resource ID combination is invalid.
+ */
+ private String getResourceNameById(PackageManager pm, String packageName, int resourceId) {
+ try {
+ Resources resources = pm.getResourcesForApplication(packageName);
+ return resources.getResourceName(resourceId);
+ } catch (NameNotFoundException e) {
+ return null;
+ } catch (NotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Scans all packages for directory content providers.
+ */
+ private void scanAllPackagesIfNeeded() {
+ String scanComplete = getDbHelper().getProperty(PROPERTY_DIRECTORY_SCAN_COMPLETE, "0");
+ if (!"0".equals(scanComplete)) {
+ return;
+ }
+
+ long start = SystemClock.currentThreadTimeMillis();
+ int count = scanAllPackages();
+ getDbHelper().setProperty(PROPERTY_DIRECTORY_SCAN_COMPLETE, "1");
+ long end = SystemClock.currentThreadTimeMillis();
+ Log.i(TAG, "Discovered " + count + " contact directories in " + (end - start) + "ms");
+
+ // Announce the change to listeners of the contacts authority
+ mContactsProvider.notifyChange(false);
+ }
+
+ public void scheduleScanAllPackages(boolean rescan) {
+ if (rescan) {
+ getDbHelper().setProperty(PROPERTY_DIRECTORY_SCAN_COMPLETE, "0");
+ }
+ if (isAlive()) {
+ getHandler().sendEmptyMessage(MESSAGE_SCAN_ALL_PROVIDERS);
+ } else {
+ scanAllPackagesIfNeeded();
+ }
+ }
+
+ /* Visible for testing */
+ int scanAllPackages() {
+ SQLiteDatabase db = getDbHelper().getWritableDatabase();
+ insertDefaultDirectory(db);
+ insertLocalInvisibleDirectory(db);
+
+ int count = 0;
+ PackageManager pm = mContext.getPackageManager();
+ List<PackageInfo> packages = pm.getInstalledPackages(
+ PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA);
+ if (packages != null) {
+ for (PackageInfo packageInfo : packages) {
+ // Check all packages except the one containing ContactsProvider itself
+ if (!packageInfo.packageName.equals(mContext.getPackageName())) {
+ count += updateDirectoriesForPackage(packageInfo, true);
+ }
+ }
+ }
+ return count;
+ }
+
+ private void insertDefaultDirectory(SQLiteDatabase db) {
+ ContentValues values = new ContentValues();
+ values.put(Directory._ID, Directory.DEFAULT);
+ values.put(Directory.PACKAGE_NAME, mContext.getApplicationInfo().packageName);
+ values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
+ values.put(Directory.TYPE_RESOURCE_ID, R.string.default_directory);
+ values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
+ mContext.getResources().getResourceName(R.string.default_directory));
+ values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
+ values.put(Directory.SHORTCUT_SUPPORT, Directory.SHORTCUT_SUPPORT_FULL);
+ values.put(Directory.PHOTO_SUPPORT, Directory.PHOTO_SUPPORT_FULL);
+ db.replace(Tables.DIRECTORIES, null, values);
+ }
+
+ private void insertLocalInvisibleDirectory(SQLiteDatabase db) {
+ ContentValues values = new ContentValues();
+ values.put(Directory._ID, Directory.LOCAL_INVISIBLE);
+ values.put(Directory.PACKAGE_NAME, mContext.getApplicationInfo().packageName);
+ values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
+ values.put(Directory.TYPE_RESOURCE_ID, R.string.local_invisible_directory);
+ values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
+ mContext.getResources().getResourceName(R.string.local_invisible_directory));
+ values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
+ values.put(Directory.SHORTCUT_SUPPORT, Directory.SHORTCUT_SUPPORT_FULL);
+ values.put(Directory.PHOTO_SUPPORT, Directory.PHOTO_SUPPORT_FULL);
+ db.replace(Tables.DIRECTORIES, null, values);
+ }
+
+ /**
+ * Scans the specified package for content directories. The package may have
+ * already been removed, so packageName does not necessarily correspond to
+ * an installed package.
+ */
+ public void onPackageChanged(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ PackageInfo packageInfo = null;
+
+ try {
+ packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ // The package got removed
+ packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ }
+
+ updateDirectoriesForPackage(packageInfo, false);
+ }
+
+ /**
+ * Scans the specified package for content directories and updates the {@link Directory}
+ * table accordingly.
+ */
+ private int updateDirectoriesForPackage(PackageInfo packageInfo, boolean initialScan) {
+ ArrayList<DirectoryInfo> directories = Lists.newArrayList();
+
+ ProviderInfo[] providers = packageInfo.providers;
+ if (providers != null) {
+ for (ProviderInfo provider : providers) {
+ Bundle metaData = provider.metaData;
+ if (metaData != null) {
+ Object trueFalse = metaData.get(CONTACT_DIRECTORY_META_DATA);
+ if (trueFalse != null && Boolean.TRUE.equals(trueFalse)) {
+ queryDirectoriesForAuthority(directories, provider);
+ }
+ }
+ }
+ }
+
+ if (directories.size() == 0 && initialScan) {
+ return 0;
+ }
+
+ SQLiteDatabase db = getDbHelper().getWritableDatabase();
+ db.beginTransaction();
+ try {
+ updateDirectories(db, directories);
+ // Clear out directories that are no longer present
+ StringBuilder sb = new StringBuilder(Directory.PACKAGE_NAME + "=?");
+ if (!directories.isEmpty()) {
+ sb.append(" AND " + Directory._ID + " NOT IN(");
+ for (DirectoryInfo info: directories) {
+ sb.append(info.id).append(",");
+ }
+ sb.setLength(sb.length() - 1); // Remove the extra comma
+ sb.append(")");
+ }
+ db.delete(Tables.DIRECTORIES, sb.toString(), new String[] { packageInfo.packageName });
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ mContactsProvider.resetDirectoryCache();
+ return directories.size();
+ }
+
+ /**
+ * Sends a {@link Directory#CONTENT_URI} request to a specific contact directory
+ * provider and appends all discovered directories to the directoryInfo list.
+ */
+ protected void queryDirectoriesForAuthority(
+ ArrayList<DirectoryInfo> directoryInfo, ProviderInfo provider) {
+ Uri uri = new Uri.Builder().scheme("content")
+ .authority(provider.authority).appendPath("directories").build();
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(
+ uri, DirectoryQuery.PROJECTION, null, null, null);
+ if (cursor == null) {
+ Log.i(TAG, providerDescription(provider) + " returned a NULL cursor.");
+ } else {
+ while (cursor.moveToNext()) {
+ DirectoryInfo info = new DirectoryInfo();
+ info.packageName = provider.packageName;
+ info.authority = provider.authority;
+ info.accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
+ info.accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
+ info.displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
+ if (!cursor.isNull(DirectoryQuery.TYPE_RESOURCE_ID)) {
+ info.typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+ }
+ if (!cursor.isNull(DirectoryQuery.EXPORT_SUPPORT)) {
+ int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
+ switch (exportSupport) {
+ case Directory.EXPORT_SUPPORT_NONE:
+ case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY:
+ case Directory.EXPORT_SUPPORT_ANY_ACCOUNT:
+ info.exportSupport = exportSupport;
+ break;
+ default:
+ Log.e(TAG, providerDescription(provider)
+ + " - invalid export support flag: " + exportSupport);
+ }
+ }
+ if (!cursor.isNull(DirectoryQuery.SHORTCUT_SUPPORT)) {
+ int shortcutSupport = cursor.getInt(DirectoryQuery.SHORTCUT_SUPPORT);
+ switch (shortcutSupport) {
+ case Directory.SHORTCUT_SUPPORT_NONE:
+ case Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY:
+ case Directory.SHORTCUT_SUPPORT_FULL:
+ info.shortcutSupport = shortcutSupport;
+ break;
+ default:
+ Log.e(TAG, providerDescription(provider)
+ + " - invalid shortcut support flag: " + shortcutSupport);
+ }
+ }
+ if (!cursor.isNull(DirectoryQuery.PHOTO_SUPPORT)) {
+ int photoSupport = cursor.getInt(DirectoryQuery.PHOTO_SUPPORT);
+ switch (photoSupport) {
+ case Directory.PHOTO_SUPPORT_NONE:
+ case Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY:
+ case Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY:
+ case Directory.PHOTO_SUPPORT_FULL:
+ info.photoSupport = photoSupport;
+ break;
+ default:
+ Log.e(TAG, providerDescription(provider)
+ + " - invalid photo support flag: " + photoSupport);
+ }
+ }
+ directoryInfo.add(info);
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(TAG, providerDescription(provider) + " exception", t);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * Updates the directories tables in the database to match the info received
+ * from directory providers.
+ */
+ private void updateDirectories(SQLiteDatabase db, ArrayList<DirectoryInfo> directoryInfo) {
+ PackageManager pm = mContext.getPackageManager();
+
+ // Insert or replace existing directories.
+ // This happens so infrequently that we can use a less-then-optimal one-a-time approach
+ for (DirectoryInfo info : directoryInfo) {
+ ContentValues values = new ContentValues();
+ values.put(Directory.PACKAGE_NAME, info.packageName);
+ values.put(Directory.DIRECTORY_AUTHORITY, info.authority);
+ values.put(Directory.ACCOUNT_NAME, info.accountName);
+ values.put(Directory.ACCOUNT_TYPE, info.accountType);
+ values.put(Directory.TYPE_RESOURCE_ID, info.typeResourceId);
+ values.put(Directory.DISPLAY_NAME, info.displayName);
+ values.put(Directory.EXPORT_SUPPORT, info.exportSupport);
+ values.put(Directory.SHORTCUT_SUPPORT, info.shortcutSupport);
+ values.put(Directory.PHOTO_SUPPORT, info.photoSupport);
+
+ if (info.typeResourceId != 0) {
+ String resourceName = getResourceNameById(
+ pm, info.packageName, info.typeResourceId);
+ values.put(DirectoryColumns.TYPE_RESOURCE_NAME, resourceName);
+ }
+
+ Cursor cursor = db.query(Tables.DIRECTORIES, new String[] { Directory._ID },
+ Directory.PACKAGE_NAME + "=? AND " + Directory.DIRECTORY_AUTHORITY + "=? AND "
+ + Directory.ACCOUNT_NAME + "=? AND " + Directory.ACCOUNT_TYPE + "=?",
+ new String[] {
+ info.packageName, info.authority, info.accountName, info.accountType },
+ null, null, null);
+ try {
+ long id;
+ if (cursor.moveToFirst()) {
+ id = cursor.getLong(0);
+ db.update(Tables.DIRECTORIES, values, Directory._ID + "=?",
+ new String[] { String.valueOf(id) });
+ } else {
+ id = db.insert(Tables.DIRECTORIES, null, values);
+ }
+ info.id = id;
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ protected String providerDescription(ProviderInfo provider) {
+ return "Directory provider " + provider.packageName + "(" + provider.authority + ")";
+ }
+}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 8f1253a..64976d5 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -16,7 +16,7 @@
package com.android.providers.contacts;
-import com.android.internal.content.SyncStateContentProviderHelper;
+import com.android.common.content.SyncStateContentProviderHelper;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -34,28 +34,31 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
+import android.location.CountryDetector;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.BaseColumns;
-import android.provider.ContactsContract;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.FullNameStyle;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.Settings;
-import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.Photo;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.FullNameStyle;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.StatusUpdates;
import android.provider.SocialContract.Activities;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
@@ -85,7 +88,7 @@
* 400-499 Honeycomb
* </pre>
*/
- static final int DATABASE_VERSION = 353;
+ static final int DATABASE_VERSION = 414;
private static final String DATABASE_NAME = "contacts2.db";
private static final String DATABASE_PRESENCE = "presence_db";
@@ -105,11 +108,12 @@
public static final String AGGREGATED_PRESENCE = "agg_presence";
public static final String NICKNAME_LOOKUP = "nickname_lookup";
public static final String CALLS = "calls";
- public static final String CONTACT_ENTITIES = "contact_entities_view";
- public static final String CONTACT_ENTITIES_RESTRICTED = "contact_entities_view_restricted";
public static final String STATUS_UPDATES = "status_updates";
public static final String PROPERTIES = "properties";
public static final String ACCOUNTS = "accounts";
+ public static final String VISIBLE_CONTACTS = "visible_contacts";
+ public static final String DIRECTORIES = "directories";
+ public static final String DEFAULT_DIRECTORY = "default_directory";
public static final String DATA_JOIN_MIMETYPES = "data "
+ "JOIN mimetypes ON (data.mimetype_id = mimetypes._id)";
@@ -184,6 +188,12 @@
public static final String CONTACTS_ALL = "view_contacts";
public static final String CONTACTS_RESTRICTED = "view_contacts_restricted";
+ public static final String ENTITIES = "view_entities";
+ public static final String ENTITIES_RESTRICTED = "view_entities_restricted";
+
+ public static final String RAW_ENTITIES = "view_raw_entities";
+ public static final String RAW_ENTITIES_RESTRICTED = "view_raw_entities_restricted";
+
public static final String GROUPS_ALL = "view_groups";
}
@@ -225,6 +235,11 @@
final String GROUP_HAS_ACCOUNT_AND_SOURCE_ID = Groups.SOURCE_ID + "=? AND "
+ Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?";
+
+ public static final String CONTACT_VISIBLE =
+ "EXISTS (SELECT _id FROM " + Tables.VISIBLE_CONTACTS
+ + " WHERE " + Tables.CONTACTS +"." + Contacts._ID
+ + "=" + Tables.VISIBLE_CONTACTS +"." + Contacts._ID + ")";
}
public interface ContactsColumns {
@@ -282,7 +297,6 @@
public static final String DISPLAY_NAME = RawContacts.DISPLAY_NAME_PRIMARY;
public static final String DISPLAY_NAME_SOURCE = RawContacts.DISPLAY_NAME_SOURCE;
public static final String AGGREGATION_NEEDED = "aggregation_needed";
- public static final String CONTACT_IN_VISIBLE_GROUP = "contact_in_visible_group";
public static final String CONCRETE_DISPLAY_NAME =
Tables.RAW_CONTACTS + "." + DISPLAY_NAME;
@@ -462,6 +476,10 @@
String PROPERTY_VALUE = "property_value";
}
+ public static final class DirectoryColumns {
+ public static final String TYPE_RESOURCE_NAME = "typeResourceName";
+ }
+
/** In-memory cache of previously found MIME-type mappings */
private final HashMap<String, Long> mMimetypeCache = new HashMap<String, Long>();
/** In-memory cache of previously found package name mappings */
@@ -481,12 +499,6 @@
private final Context mContext;
private final SyncStateContentProviderHelper mSyncState;
-
- /** Compiled statements for updating {@link Contacts#IN_VISIBLE_GROUP}. */
- private SQLiteStatement mVisibleSpecificUpdate;
- private SQLiteStatement mVisibleUpdateRawContacts;
- private SQLiteStatement mVisibleSpecificUpdateRawContacts;
-
private boolean mReopenDatabase = false;
private static ContactsDatabaseHelper sSingleton = null;
@@ -511,7 +523,6 @@
*/
ContactsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
- if (false) Log.i(TAG, "Creating OpenHelper");
Resources resources = context.getResources();
mContext = context;
@@ -552,34 +563,6 @@
+ " FROM " + Tables.ACTIVITIES_JOIN_MIMETYPES + " WHERE " + Tables.ACTIVITIES + "."
+ Activities._ID + "=?");
- // Change visibility of a specific contact
- mVisibleSpecificUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.IN_VISIBLE_GROUP + "=(" + Clauses.CONTACT_IS_VISIBLE + ")" +
- " WHERE " + ContactsColumns.CONCRETE_ID + "=?");
-
- // Return visibility of the aggregate contact joined with the raw contact
- String contactVisibility =
- "SELECT " + Contacts.IN_VISIBLE_GROUP +
- " FROM " + Tables.CONTACTS +
- " WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID;
-
- // Set visibility of raw contacts to the visibility of corresponding aggregate contacts
- mVisibleUpdateRawContacts = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "=(CASE WHEN ("
- + contactVisibility + ")=1 THEN 1 ELSE 0 END)" +
- " WHERE " + RawContacts.DELETED + "=0" +
- " AND " + RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "!=("
- + contactVisibility + ")=1");
-
- // Set visibility of a raw contact to the visibility of corresponding aggregate contact
- mVisibleSpecificUpdateRawContacts = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "=("
- + contactVisibility + ")" +
- " WHERE " + RawContacts.DELETED + "=0 AND " + RawContacts.CONTACT_ID + "=?");
-
db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";");
db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+
StatusUpdates.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
@@ -604,7 +587,7 @@
+ DATABASE_PRESENCE + "." + Tables.AGGREGATED_PRESENCE + " ("+
AggregatedPresenceColumns.CONTACT_ID
+ " INTEGER PRIMARY KEY REFERENCES contacts(_id)," +
- StatusUpdates.PRESENCE_STATUS + " INTEGER," +
+ StatusUpdates.PRESENCE + " INTEGER," +
StatusUpdates.CHAT_CAPABILITY + " INTEGER NOT NULL DEFAULT 0" +
");");
@@ -678,17 +661,12 @@
Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
Contacts.LAST_TIME_CONTACTED + " INTEGER," +
Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
- Contacts.IN_VISIBLE_GROUP + " INTEGER NOT NULL DEFAULT 1," +
Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," +
Contacts.LOOKUP_KEY + " TEXT," +
ContactsColumns.LAST_STATUS_UPDATE_ID + " INTEGER REFERENCES data(_id)," +
ContactsColumns.SINGLE_IS_RESTRICTED + " INTEGER NOT NULL DEFAULT 0" +
");");
- db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" +
- Contacts.IN_VISIBLE_GROUP +
- ");");
-
db.execSQL("CREATE INDEX contacts_has_phone_index ON " + Tables.CONTACTS + " (" +
Contacts.HAS_PHONE_NUMBER +
");");
@@ -708,6 +686,7 @@
RawContacts.ACCOUNT_NAME + " STRING DEFAULT NULL, " +
RawContacts.ACCOUNT_TYPE + " STRING DEFAULT NULL, " +
RawContacts.SOURCE_ID + " TEXT," +
+ RawContacts.RAW_CONTACT_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
RawContacts.VERSION + " INTEGER NOT NULL DEFAULT 1," +
RawContacts.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
RawContacts.DELETED + " INTEGER NOT NULL DEFAULT 0," +
@@ -731,7 +710,6 @@
RawContacts.SORT_KEY_ALTERNATIVE + " TEXT COLLATE " +
ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
RawContacts.NAME_VERIFIED + " INTEGER NOT NULL DEFAULT 0," +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + " INTEGER NOT NULL DEFAULT 0," +
RawContacts.SYNC1 + " TEXT, " +
RawContacts.SYNC2 + " TEXT, " +
RawContacts.SYNC3 + " TEXT, " +
@@ -776,6 +754,7 @@
DataColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," +
DataColumns.MIMETYPE_ID + " INTEGER REFERENCES mimetype(_id) NOT NULL," +
Data.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
+ Data.IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
Data.IS_PRIMARY + " INTEGER NOT NULL DEFAULT 0," +
Data.IS_SUPER_PRIMARY + " INTEGER NOT NULL DEFAULT 0," +
Data.DATA_VERSION + " INTEGER NOT NULL DEFAULT 0," +
@@ -815,7 +794,7 @@
// Private phone numbers table used for lookup
db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" +
PhoneLookupColumns.DATA_ID
- + " INTEGER PRIMARY KEY REFERENCES data(_id) NOT NULL," +
+ + " INTEGER REFERENCES data(_id) NOT NULL," +
PhoneLookupColumns.RAW_CONTACT_ID
+ " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," +
@@ -878,6 +857,8 @@
Groups.DELETED + " INTEGER NOT NULL DEFAULT 0," +
Groups.GROUP_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
Groups.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1," +
+ Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0," +
+ Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0," +
Groups.SYNC1 + " TEXT, " +
Groups.SYNC2 + " TEXT, " +
Groups.SYNC3 + " TEXT, " +
@@ -920,6 +901,14 @@
Settings.ACCOUNT_TYPE + ") ON CONFLICT REPLACE" +
");");
+ db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
+ Contacts._ID + " INTEGER PRIMARY KEY" +
+ ");");
+
+ db.execSQL("CREATE TABLE " + Tables.DEFAULT_DIRECTORY + " (" +
+ Contacts._ID + " INTEGER PRIMARY KEY" +
+ ");");
+
// The table for recent calls is here so we can do table joins
// on people, phones, and calls all in one place.
db.execSQL("CREATE TABLE " + Tables.CALLS + " (" +
@@ -931,8 +920,8 @@
Calls.NEW + " INTEGER," +
Calls.CACHED_NAME + " TEXT," +
Calls.CACHED_NUMBER_TYPE + " INTEGER," +
- Calls.CACHED_NUMBER_LABEL + " TEXT" +
- ");");
+ Calls.CACHED_NUMBER_LABEL + " TEXT," +
+ Calls.COUNTRY_ISO + " TEXT" + ");");
// Activities table
db.execSQL("CREATE TABLE " + Tables.ACTIVITIES + " (" +
@@ -976,9 +965,10 @@
// is added to the phone.
db.execSQL("INSERT INTO accounts VALUES(NULL, NULL)");
+ createDirectoriesTable(db);
+
createContactsViews(db);
createGroupsView(db);
- createContactEntitiesView(db);
createContactsTriggers(db);
createContactsIndexes(db);
@@ -1001,6 +991,28 @@
ContactsContract.AUTHORITY, new Bundle());
}
+ private void createDirectoriesTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + Tables.DIRECTORIES + "(" +
+ Directory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Directory.PACKAGE_NAME + " TEXT NOT NULL," +
+ Directory.DIRECTORY_AUTHORITY + " TEXT NOT NULL," +
+ Directory.TYPE_RESOURCE_ID + " INTEGER," +
+ DirectoryColumns.TYPE_RESOURCE_NAME + " TEXT," +
+ Directory.ACCOUNT_TYPE + " TEXT," +
+ Directory.ACCOUNT_NAME + " TEXT," +
+ Directory.DISPLAY_NAME + " TEXT, " +
+ Directory.EXPORT_SUPPORT + " INTEGER NOT NULL" +
+ " DEFAULT " + Directory.EXPORT_SUPPORT_NONE + "," +
+ Directory.SHORTCUT_SUPPORT + " INTEGER NOT NULL" +
+ " DEFAULT " + Directory.SHORTCUT_SUPPORT_NONE + "," +
+ Directory.PHOTO_SUPPORT + " INTEGER NOT NULL" +
+ " DEFAULT " + Directory.PHOTO_SUPPORT_NONE +
+ ");");
+
+ // Trigger a full scan of directories in the system
+ setProperty(db, ContactDirectoryManager.PROPERTY_DIRECTORY_SCAN_COMPLETE, "0");
+ }
+
private static void createContactsTriggers(SQLiteDatabase db) {
/*
@@ -1018,6 +1030,16 @@
+ "=OLD." + RawContacts._ID
+ " OR " + AggregationExceptions.RAW_CONTACT_ID2
+ "=OLD." + RawContacts._ID + ";"
+ + " DELETE FROM " + Tables.VISIBLE_CONTACTS
+ + " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
+ + " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
+ + " WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
+ + " )=1;"
+ + " DELETE FROM " + Tables.DEFAULT_DIRECTORY
+ + " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
+ + " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
+ + " WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
+ + " )=1;"
+ " DELETE FROM " + Tables.CONTACTS
+ " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
+ " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
@@ -1093,13 +1115,11 @@
db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key1_index");
db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "," +
RawContacts.SORT_KEY_PRIMARY +
");");
db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key2_index");
db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "," +
RawContacts.SORT_KEY_ALTERNATIVE +
");");
}
@@ -1111,6 +1131,10 @@
db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_RESTRICTED + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS_ALL + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS_RESTRICTED + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES_RESTRICTED + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES_RESTRICTED + ";");
String dataColumns =
Data.IS_PRIMARY + ", "
@@ -1118,6 +1142,7 @@
+ Data.DATA_VERSION + ", "
+ PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + ","
+ MimetypesColumns.MIMETYPE + " AS " + Data.MIMETYPE + ", "
+ + Data.IS_READ_ONLY + ", "
+ Data.DATA1 + ", "
+ Data.DATA2 + ", "
+ Data.DATA3 + ", "
@@ -1150,6 +1175,14 @@
+ RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + ","
+ RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4;
+ String baseContactColumns =
+ Contacts.HAS_PHONE_NUMBER + ", "
+ + Contacts.NAME_RAW_CONTACT_ID + ", "
+ + Contacts.LOOKUP_KEY + ", "
+ + Contacts.PHOTO_ID + ", "
+ + Clauses.CONTACT_VISIBLE + " AS " + Contacts.IN_VISIBLE_GROUP + ", "
+ + ContactsColumns.LAST_STATUS_UPDATE_ID;
+
String contactOptionColumns =
ContactsColumns.CONCRETE_CUSTOM_RINGTONE
+ " AS " + RawContacts.CUSTOM_RINGTONE + ","
@@ -1176,9 +1209,7 @@
+ "name_raw_contact." + RawContacts.SORT_KEY_PRIMARY
+ " AS " + Contacts.SORT_KEY_PRIMARY + ", "
+ "name_raw_contact." + RawContacts.SORT_KEY_ALTERNATIVE
- + " AS " + Contacts.SORT_KEY_ALTERNATIVE + ", "
- + "name_raw_contact." + RawContactsColumns.CONTACT_IN_VISIBLE_GROUP
- + " AS " + Contacts.IN_VISIBLE_GROUP;
+ + " AS " + Contacts.SORT_KEY_ALTERNATIVE;
String dataSelect = "SELECT "
+ DataColumns.CONCRETE_ID + " AS " + Data._ID + ","
@@ -1188,10 +1219,11 @@
+ dataColumns + ", "
+ contactOptionColumns + ", "
+ contactNameColumns + ", "
- + Contacts.LOOKUP_KEY + ", "
- + Contacts.PHOTO_ID + ", "
- + Contacts.NAME_RAW_CONTACT_ID + ","
- + ContactsColumns.LAST_STATUS_UPDATE_ID + ", "
+ + baseContactColumns + ", "
+ + buildPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
+ Contacts.PHOTO_URI) + ", "
+ + buildPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
+ Contacts.PHOTO_THUMBNAIL_URI) + ", "
+ Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
+ " FROM " + Tables.DATA
+ " JOIN " + Tables.MIMETYPES + " ON ("
@@ -1224,6 +1256,7 @@
+ RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ","
+ RawContacts.CONTACT_ID + ", "
+ RawContacts.AGGREGATION_MODE + ", "
+ + RawContacts.RAW_CONTACT_IS_READ_ONLY + ", "
+ RawContacts.DELETED + ", "
+ RawContacts.DISPLAY_NAME_SOURCE + ", "
+ RawContacts.DISPLAY_NAME_PRIMARY + ", "
@@ -1244,9 +1277,7 @@
ContactsColumns.CONCRETE_CUSTOM_RINGTONE
+ " AS " + Contacts.CUSTOM_RINGTONE + ", "
+ contactNameColumns + ", "
- + Contacts.HAS_PHONE_NUMBER + ", "
- + Contacts.LOOKUP_KEY + ", "
- + Contacts.PHOTO_ID + ", "
+ + baseContactColumns + ", "
+ ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
+ " AS " + Contacts.LAST_TIME_CONTACTED + ", "
+ ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
@@ -1254,12 +1285,13 @@
+ ContactsColumns.CONCRETE_STARRED
+ " AS " + Contacts.STARRED + ", "
+ ContactsColumns.CONCRETE_TIMES_CONTACTED
- + " AS " + Contacts.TIMES_CONTACTED + ", "
- + ContactsColumns.LAST_STATUS_UPDATE_ID;
+ + " AS " + Contacts.TIMES_CONTACTED;
String contactsSelect = "SELECT "
+ ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
- + contactsColumns
+ + contactsColumns + ", "
+ + buildPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_URI) + ", "
+ + buildPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_THUMBNAIL_URI)
+ " FROM " + Tables.CONTACTS
+ " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
+ Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")";
@@ -1267,79 +1299,17 @@
db.execSQL("CREATE VIEW " + Views.CONTACTS_ALL + " AS " + contactsSelect);
db.execSQL("CREATE VIEW " + Views.CONTACTS_RESTRICTED + " AS " + contactsSelect
+ " WHERE " + ContactsColumns.SINGLE_IS_RESTRICTED + "=0");
- }
- private static void createGroupsView(SQLiteDatabase db) {
- db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS_ALL + ";");
- String groupsColumns =
- Groups.ACCOUNT_NAME + ","
- + Groups.ACCOUNT_TYPE + ","
- + 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.SYNC1 + ","
- + Groups.SYNC2 + ","
- + Groups.SYNC3 + ","
- + Groups.SYNC4 + ","
- + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE;
-
- String groupsSelect = "SELECT "
- + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + ","
- + groupsColumns
- + " FROM " + Tables.GROUPS_JOIN_PACKAGES;
-
- db.execSQL("CREATE VIEW " + Views.GROUPS_ALL + " AS " + groupsSelect);
- }
-
- private static void createContactEntitiesView(SQLiteDatabase db) {
- db.execSQL("DROP VIEW IF EXISTS " + Tables.CONTACT_ENTITIES + ";");
- db.execSQL("DROP VIEW IF EXISTS " + Tables.CONTACT_ENTITIES_RESTRICTED + ";");
-
- String contactEntitiesSelect = "SELECT "
- + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + ","
- + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + ","
- + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + ","
- + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + ","
- + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + ","
- + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
- + RawContactsColumns.CONCRETE_NAME_VERIFIED + " AS " + RawContacts.NAME_VERIFIED + ","
- + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + ","
+ String rawEntitiesSelect = "SELECT "
+ RawContacts.CONTACT_ID + ", "
- + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + ", "
- + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + ", "
- + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + ", "
- + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4 + ", "
- + Data.MIMETYPE + ", "
- + Data.DATA1 + ", "
- + Data.DATA2 + ", "
- + Data.DATA3 + ", "
- + Data.DATA4 + ", "
- + Data.DATA5 + ", "
- + Data.DATA6 + ", "
- + Data.DATA7 + ", "
- + Data.DATA8 + ", "
- + Data.DATA9 + ", "
- + Data.DATA10 + ", "
- + Data.DATA11 + ", "
- + Data.DATA12 + ", "
- + Data.DATA13 + ", "
- + Data.DATA14 + ", "
- + Data.DATA15 + ", "
+ + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
+ + dataColumns + ", "
+ + syncColumns + ", "
+ Data.SYNC1 + ", "
+ Data.SYNC2 + ", "
+ Data.SYNC3 + ", "
+ Data.SYNC4 + ", "
+ RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ", "
- + Data.IS_PRIMARY + ", "
- + Data.IS_SUPER_PRIMARY + ", "
- + Data.DATA_VERSION + ", "
+ DataColumns.CONCRETE_ID + " AS " + RawContacts.Entity.DATA_ID + ","
+ RawContactsColumns.CONCRETE_STARRED + " AS " + RawContacts.STARRED + ","
+ RawContactsColumns.CONCRETE_IS_RESTRICTED + " AS "
@@ -1357,10 +1327,92 @@
+ "' AND " + GroupsColumns.CONCRETE_ID + "="
+ Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
- db.execSQL("CREATE VIEW " + Tables.CONTACT_ENTITIES + " AS "
- + contactEntitiesSelect);
- db.execSQL("CREATE VIEW " + Tables.CONTACT_ENTITIES_RESTRICTED + " AS "
- + contactEntitiesSelect + " WHERE " + RawContacts.IS_RESTRICTED + "=0");
+ db.execSQL("CREATE VIEW " + Views.RAW_ENTITIES + " AS "
+ + rawEntitiesSelect);
+ db.execSQL("CREATE VIEW " + Views.RAW_ENTITIES_RESTRICTED + " AS "
+ + rawEntitiesSelect + " WHERE " + RawContacts.IS_RESTRICTED + "=0");
+
+ String entitiesSelect = "SELECT "
+ + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + Contacts._ID + ", "
+ + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
+ + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
+ + RawContactsColumns.CONCRETE_IS_RESTRICTED
+ + " AS " + RawContacts.IS_RESTRICTED + ","
+ + dataColumns + ", "
+ + syncColumns + ", "
+ + contactsColumns + ", "
+ + buildPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
+ Contacts.PHOTO_URI) + ", "
+ + buildPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
+ Contacts.PHOTO_THUMBNAIL_URI) + ", "
+ + Data.SYNC1 + ", "
+ + Data.SYNC2 + ", "
+ + Data.SYNC3 + ", "
+ + Data.SYNC4 + ", "
+ + RawContactsColumns.CONCRETE_ID + " AS " + Contacts.Entity.RAW_CONTACT_ID + ", "
+ + DataColumns.CONCRETE_ID + " AS " + Contacts.Entity.DATA_ID + ","
+ + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
+ + " FROM " + Tables.RAW_CONTACTS
+ + " JOIN " + Tables.CONTACTS + " ON ("
+ + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
+ + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
+ + Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
+ + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
+ + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
+ + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
+ + DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
+ + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
+ + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
+ + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
+ + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
+ + "' AND " + GroupsColumns.CONCRETE_ID + "="
+ + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
+
+ db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS "
+ + entitiesSelect);
+ db.execSQL("CREATE VIEW " + Views.ENTITIES_RESTRICTED + " AS "
+ + entitiesSelect + " WHERE " + RawContactsColumns.CONCRETE_IS_RESTRICTED + "=0");
+ }
+
+ private static String buildPhotoUriAlias(String contactIdColumn, String alias) {
+ return "(CASE WHEN " + Contacts.PHOTO_ID + " IS NULL"
+ + " OR " + Contacts.PHOTO_ID + "=0"
+ + " THEN NULL"
+ + " ELSE " + "'" + Contacts.CONTENT_URI + "/'||"
+ + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
+ + " END)"
+ + " AS " + alias;
+ }
+
+ private static void createGroupsView(SQLiteDatabase db) {
+ db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS_ALL + ";");
+ String groupsColumns =
+ Groups.ACCOUNT_NAME + ","
+ + Groups.ACCOUNT_TYPE + ","
+ + 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.SYNC1 + ","
+ + Groups.SYNC2 + ","
+ + Groups.SYNC3 + ","
+ + Groups.SYNC4 + ","
+ + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE;
+
+ String groupsSelect = "SELECT "
+ + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + ","
+ + groupsColumns
+ + " FROM " + Tables.GROUPS_JOIN_PACKAGES;
+
+ db.execSQL("CREATE VIEW " + Views.GROUPS_ALL + " AS " + groupsSelect);
}
@Override
@@ -1526,10 +1578,92 @@
oldVersion = 353;
}
+ // Honeycomb upgrades
+ if (oldVersion < 400) {
+ upgradeViewsAndTriggers = true;
+ upgradeToVersion400(db);
+ oldVersion = 400;
+ }
+
+ if (oldVersion == 400) {
+ upgradeViewsAndTriggers = true;
+ upgradeToVersion401(db);
+ oldVersion = 401;
+ }
+
+ if (oldVersion == 401) {
+ upgradeToVersion402(db);
+ oldVersion = 402;
+ }
+
+ if (oldVersion == 402) {
+ upgradeViewsAndTriggers = true;
+ upgradeToVersion403(db);
+ oldVersion = 403;
+ }
+
+ if (oldVersion == 403) {
+ upgradeViewsAndTriggers = true;
+ oldVersion = 404;
+ }
+
+ if (oldVersion == 404) {
+ upgradeViewsAndTriggers = true;
+ upgradeToVersion405(db);
+ oldVersion = 405;
+ }
+
+ if (oldVersion == 405) {
+ upgradeViewsAndTriggers = true;
+ upgradeToVersion406(db);
+ oldVersion = 406;
+ }
+
+ if (oldVersion == 406) {
+ upgradeViewsAndTriggers = true;
+ oldVersion = 407;
+ }
+
+ if (oldVersion == 407) {
+ // Obsolete
+ oldVersion = 408;
+ }
+
+ if (oldVersion == 408) {
+ upgradeViewsAndTriggers = true;
+ upgradeToVersion409(db);
+ oldVersion = 409;
+ }
+
+ if (oldVersion == 409) {
+ upgradeViewsAndTriggers = true;
+ oldVersion = 410;
+ }
+
+ if (oldVersion == 410) {
+ upgradeToVersion411(db);
+ oldVersion = 411;
+ }
+
+ if (oldVersion == 411) {
+ // Same upgrade as 353, only on Honeycomb devices
+ upgradeToVersion353(db);
+ oldVersion = 412;
+ }
+
+ if (oldVersion == 412) {
+ upgradeToVersion413(db);
+ oldVersion = 413;
+ }
+
+ if (oldVersion == 413) {
+ upgradeNameLookup = true;
+ oldVersion = 414;
+ }
+
if (upgradeViewsAndTriggers) {
createContactsViews(db);
createGroupsView(db);
- createContactEntitiesView(db);
createContactsTriggers(db);
createContactsIndexes(db);
LegacyApiSupport.createViews(db);
@@ -1600,8 +1734,7 @@
" ADD " + Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)");
db.execSQL(
"ALTER TABLE " + Tables.RAW_CONTACTS +
- " ADD " + RawContactsColumns.CONTACT_IN_VISIBLE_GROUP
- + " INTEGER NOT NULL DEFAULT 0");
+ " ADD contact_in_visible_group INTEGER NOT NULL DEFAULT 0");
// For each Contact, find the RawContact that contributed the display name
db.execSQL(
@@ -1643,7 +1776,7 @@
// indexing on (display_name, in_visible_group)
db.execSQL(
"UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "=(" +
+ " SET contact_in_visible_group=(" +
"SELECT " + Contacts.IN_VISIBLE_GROUP +
" FROM " + Tables.CONTACTS +
" WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID + ")" +
@@ -1651,7 +1784,7 @@
);
db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "," +
+ "contact_in_visible_group" + "," +
RawContactsColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC" +
");");
@@ -1695,12 +1828,12 @@
db.execSQL("DROP INDEX raw_contact_sort_key1_index");
db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "," +
+ "contact_in_visible_group" + "," +
RawContacts.SORT_KEY_PRIMARY +
");");
db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP + "," +
+ "contact_in_visible_group" + "," +
RawContacts.SORT_KEY_ALTERNATIVE +
");");
}
@@ -2141,13 +2274,20 @@
}
private void upgradeToVersion308(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE accounts (" +
- "account_name TEXT, " +
- "account_type TEXT " +
- ");");
+ db.execSQL("CREATE TABLE accounts (" +
+ "account_name TEXT, " +
+ "account_type TEXT " +
+ ");");
- db.execSQL("INSERT INTO accounts " +
- "SELECT DISTINCT account_name, account_type FROM raw_contacts");
+ db.execSQL("INSERT INTO accounts " +
+ "SELECT DISTINCT account_name, account_type FROM raw_contacts");
+ }
+
+ private void upgradeToVersion400(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE " + Tables.GROUPS
+ + " ADD " + Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL("ALTER TABLE " + Tables.GROUPS
+ + " ADD " + Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0;");
}
private void upgradeToVersion353(SQLiteDatabase db) {
@@ -2440,6 +2580,169 @@
stmt.executeInsert();
}
+ /**
+ * Changing the VISIBLE bit from a field on both RawContacts and Contacts to a separate table.
+ */
+ private void upgradeToVersion401(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
+ Contacts._ID + " INTEGER PRIMARY KEY" +
+ ");");
+ db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS +
+ " SELECT " + Contacts._ID +
+ " FROM " + Tables.CONTACTS +
+ " WHERE " + Contacts.IN_VISIBLE_GROUP + "!=0");
+ db.execSQL("DROP INDEX contacts_visible_index");
+ }
+
+ /**
+ * Introducing a new table: directories.
+ */
+ private void upgradeToVersion402(SQLiteDatabase db) {
+ createDirectoriesTable(db);
+ }
+
+ private void upgradeToVersion403(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS directories;");
+ createDirectoriesTable(db);
+
+ db.execSQL("ALTER TABLE raw_contacts"
+ + " ADD raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0;");
+
+ db.execSQL("ALTER TABLE data"
+ + " ADD is_read_only INTEGER NOT NULL DEFAULT 0;");
+ }
+
+ private void upgradeToVersion405(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS phone_lookup;");
+ // Private phone numbers table used for lookup
+ db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" +
+ PhoneLookupColumns.DATA_ID
+ + " INTEGER REFERENCES data(_id) NOT NULL," +
+ PhoneLookupColumns.RAW_CONTACT_ID
+ + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
+ PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," +
+ PhoneLookupColumns.MIN_MATCH + " TEXT NOT NULL" +
+ ");");
+
+ db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" +
+ PhoneLookupColumns.NORMALIZED_NUMBER + "," +
+ PhoneLookupColumns.RAW_CONTACT_ID + "," +
+ PhoneLookupColumns.DATA_ID +
+ ");");
+
+ db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
+ PhoneLookupColumns.MIN_MATCH + "," +
+ PhoneLookupColumns.RAW_CONTACT_ID + "," +
+ PhoneLookupColumns.DATA_ID +
+ ");");
+
+ final long mimeTypeId = lookupMimeTypeId(db, Phone.CONTENT_ITEM_TYPE);
+ if (mimeTypeId == -1) {
+ return;
+ }
+
+ String mCountryIso = getCountryIso();
+ Cursor cursor = db.rawQuery(
+ "SELECT _id, " + Phone.RAW_CONTACT_ID + ", " + Phone.NUMBER +
+ " FROM " + Tables.DATA +
+ " WHERE " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId
+ + " AND " + Phone.NUMBER + " NOT NULL", null);
+
+ ContentValues phoneValues = new ContentValues();
+ try {
+ while (cursor.moveToNext()) {
+ long dataID = cursor.getLong(0);
+ long rawContactID = cursor.getLong(1);
+ String number = cursor.getString(2);
+ String numberE164 = PhoneNumberUtils.formatNumberToE164(number, mCountryIso);
+ String normalizedNumber = PhoneNumberUtils.normalizeNumber(number);
+ if (!TextUtils.isEmpty(normalizedNumber)) {
+ phoneValues.clear();
+ phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactID);
+ phoneValues.put(PhoneLookupColumns.DATA_ID, dataID);
+ phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber);
+ phoneValues.put(PhoneLookupColumns.MIN_MATCH,
+ PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber));
+ db.insert(Tables.PHONE_LOOKUP, null, phoneValues);
+
+ if (numberE164 != null && !numberE164.equals(normalizedNumber)) {
+ phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, numberE164);
+ phoneValues.put(PhoneLookupColumns.MIN_MATCH,
+ PhoneNumberUtils.toCallerIDMinMatch(numberE164));
+ db.insert(Tables.PHONE_LOOKUP, null, phoneValues);
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void upgradeToVersion406(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE calls ADD countryiso TEXT;");
+ }
+
+ private void upgradeToVersion409(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS directories;");
+ createDirectoriesTable(db);
+ }
+
+ /**
+ * Adding DEFAULT_DIRECTORY table.
+ */
+ private void upgradeToVersion411(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.DEFAULT_DIRECTORY);
+ db.execSQL("CREATE TABLE " + Tables.DEFAULT_DIRECTORY + " (" +
+ Contacts._ID + " INTEGER PRIMARY KEY" +
+ ");");
+
+ // Process contacts without an account
+ db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
+ " SELECT " + RawContacts.CONTACT_ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " IS NULL " +
+ " AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " IS NULL ");
+
+ // Process accounts that don't have a default group (e.g. Exchange)
+ db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
+ " SELECT " + RawContacts.CONTACT_ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE NOT EXISTS" +
+ " (SELECT " + Groups._ID +
+ " FROM " + Tables.GROUPS +
+ " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " = "
+ + GroupsColumns.CONCRETE_ACCOUNT_NAME +
+ " AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " = "
+ + GroupsColumns.CONCRETE_ACCOUNT_TYPE +
+ " AND " + Groups.AUTO_ADD + " != 0" +
+ ")");
+
+ long mimetype = lookupMimeTypeId(db, GroupMembership.CONTENT_ITEM_TYPE);
+
+ // Process accounts that do have a default group (e.g. Google)
+ db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
+ " SELECT " + RawContacts.CONTACT_ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " JOIN " + Tables.DATA +
+ " ON (" + RawContactsColumns.CONCRETE_ID + "=" + Data.RAW_CONTACT_ID + ")" +
+ " WHERE " + DataColumns.MIMETYPE_ID + "=" + mimetype +
+ " AND EXISTS" +
+ " (SELECT " + Groups._ID +
+ " FROM " + Tables.GROUPS +
+ " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " = "
+ + GroupsColumns.CONCRETE_ACCOUNT_NAME +
+ " AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " = "
+ + GroupsColumns.CONCRETE_ACCOUNT_TYPE +
+ " AND " + Groups.AUTO_ADD + " != 0" +
+ ")");
+ }
+
+ private void upgradeToVersion413(SQLiteDatabase db) {
+ db.execSQL(
+ "ALTER TABLE " + Tables.DIRECTORIES +
+ " ADD " + DirectoryColumns.TYPE_RESOURCE_NAME + " TEXT;");
+ }
+
public String extractHandleFromEmailAddress(String email) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
if (tokens.length == 0) {
@@ -2495,8 +2798,6 @@
"contacts_restricted_index", "10000 9000");
updateIndexStats(db, Tables.CONTACTS,
"contacts_has_phone_index", "10000 500");
- updateIndexStats(db, Tables.CONTACTS,
- "contacts_visible_index", "10000 500 1");
updateIndexStats(db, Tables.RAW_CONTACTS,
"raw_contacts_source_id_index", "10000 1 1 1");
@@ -2575,6 +2876,7 @@
db.execSQL("DELETE FROM " + Tables.SETTINGS + ";");
db.execSQL("DELETE FROM " + Tables.ACTIVITIES + ";");
db.execSQL("DELETE FROM " + Tables.CALLS + ";");
+ db.execSQL("DELETE FROM " + Tables.DIRECTORIES + ";");
// Note: we are not removing reference data from Tables.NICKNAME_LOOKUP
}
@@ -2704,67 +3006,90 @@
* Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts.
*/
public void updateAllVisible() {
- SQLiteDatabase db = getWritableDatabase();
- final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
- String[] selectionArgs = new String[]{String.valueOf(groupMembershipMimetypeId)};
-
- // There are a couple questions that can be asked regarding the
- // following two update statements:
- //
- // Q: Why do we run these two queries separately? They seem like they could be combined.
- // A: This is a result of painstaking experimentation. Turns out that the most
- // important optimization is to make sure we never update a value to its current value.
- // Changing 0 to 0 is unexpectedly expensive - SQLite actually writes the unchanged
- // rows back to disk. The other consideration is that the CONTACT_IS_VISIBLE condition
- // is very complex and executing it twice in the same statement ("if contact_visible !=
- // CONTACT_IS_VISIBLE change it to CONTACT_IS_VISIBLE") is more expensive than running
- // two update statements.
- //
- // Q: How come we are using db.update instead of compiled statements?
- // A: This is a limitation of the compiled statement API. It does not return the
- // number of rows changed. As you will see later in this method we really need
- // to know how many rows have been changed.
-
- // First update contacts that are currently marked as invisible, but need to be visible
- ContentValues values = new ContentValues();
- values.put(Contacts.IN_VISIBLE_GROUP, 1);
- int countMadeVisible = db.update(Tables.CONTACTS, values,
- Contacts.IN_VISIBLE_GROUP + "=0" + " AND (" + Clauses.CONTACT_IS_VISIBLE + ")=1",
- selectionArgs);
-
- // Next update contacts that are currently marked as visible, but need to be invisible
- values.put(Contacts.IN_VISIBLE_GROUP, 0);
- int countMadeInvisible = db.update(Tables.CONTACTS, values,
- Contacts.IN_VISIBLE_GROUP + "=1" + " AND (" + Clauses.CONTACT_IS_VISIBLE + ")=0",
- selectionArgs);
-
- if (countMadeVisible != 0 || countMadeInvisible != 0) {
- // TODO break out the fields (contact_in_visible_group, sort_key, sort_key_alt) into
- // a separate table.
- // Rationale: The following statement will take a very long time on
- // a large database even though we are only changing one field from 0 to 1 or from
- // 1 to 0. The reason for the slowness is that SQLite will need to write the whole
- // page even when only one bit on it changes. Changing the visibility of a
- // significant number of contacts will likely read and write almost the entire
- // raw_contacts table. So, the solution is to break out into a separate table
- // the changing field along with the sort keys used for index-based sorting.
- // That table will occupy a smaller number of pages, so rewriting it would
- // not be as expensive.
- mVisibleUpdateRawContacts.execute();
- }
+ updateCustomContactVisibility(getWritableDatabase(), "");
}
/**
- * Update {@link Contacts#IN_VISIBLE_GROUP} for a specific contact.
+ * Update {@link Contacts#IN_VISIBLE_GROUP} and
+ * {@link Tables#DEFAULT_DIRECTORY} for a specific contact.
*/
public void updateContactVisible(long contactId) {
- final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
- mVisibleSpecificUpdate.bindLong(1, groupMembershipMimetypeId);
- mVisibleSpecificUpdate.bindLong(2, contactId);
- mVisibleSpecificUpdate.execute();
+ SQLiteDatabase db = getWritableDatabase();
+ updateCustomContactVisibility(getWritableDatabase(),
+ " AND " + Contacts._ID + "=" + contactId);
- mVisibleSpecificUpdateRawContacts.bindLong(1, contactId);
- mVisibleSpecificUpdateRawContacts.execute();
+ String contactIdAsString = String.valueOf(contactId);
+ long mimetype = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
+
+ // The contact will be included in the default directory if contains
+ // a raw contact that is in any group or in an account that
+ // does not have any AUTO_ADD groups.
+ long visibleRawContact = DatabaseUtils.longForQuery(db,
+ "SELECT EXISTS (" +
+ "SELECT " + RawContacts.CONTACT_ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " JOIN " + Tables.DATA +
+ " ON (" + RawContactsColumns.CONCRETE_ID + "="
+ + Data.RAW_CONTACT_ID + ")" +
+ " WHERE " + RawContacts.CONTACT_ID + "=?" +
+ " AND " + DataColumns.MIMETYPE_ID + "=?" +
+ ") OR EXISTS (" +
+ "SELECT " + RawContacts._ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + "=?" +
+ " AND NOT EXISTS" +
+ " (SELECT " + Groups._ID +
+ " FROM " + Tables.GROUPS +
+ " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " = "
+ + GroupsColumns.CONCRETE_ACCOUNT_NAME +
+ " AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " = "
+ + GroupsColumns.CONCRETE_ACCOUNT_TYPE +
+ " AND " + Groups.AUTO_ADD + " != 0" +
+ ")" +
+ ") OR EXISTS (" +
+ "SELECT " + RawContacts._ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + "=?" +
+ " AND " + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " IS NULL " +
+ " AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " IS NULL" +
+ ")",
+ new String[] {
+ contactIdAsString,
+ String.valueOf(mimetype),
+ contactIdAsString,
+ contactIdAsString
+ });
+
+ if (visibleRawContact != 0) {
+ db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + " VALUES(?)",
+ new String[] { contactIdAsString });
+ } else {
+ db.execSQL("DELETE FROM " + Tables.DEFAULT_DIRECTORY + " WHERE " + Contacts._ID + "=?",
+ new String[] { contactIdAsString });
+ }
+ }
+
+ private void updateCustomContactVisibility(SQLiteDatabase db, String selection) {
+ final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
+ String[] selectionArgs = new String[]{String.valueOf(groupMembershipMimetypeId)};
+
+ // First delete what needs to be deleted, then insert what needs to be added.
+ // Since flash writes are very expensive, this approach is much better than
+ // delete-all-insert-all.
+ db.execSQL("DELETE FROM " + Tables.VISIBLE_CONTACTS +
+ " WHERE " + "_id NOT IN" +
+ "(SELECT " + Contacts._ID +
+ " FROM " + Tables.CONTACTS +
+ " WHERE (" + Clauses.CONTACT_IS_VISIBLE + ")=1) " + selection,
+ selectionArgs);
+
+ db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS +
+ " SELECT " + Contacts._ID +
+ " FROM " + Tables.CONTACTS +
+ " WHERE " + Contacts._ID +
+ " NOT IN " + Tables.VISIBLE_CONTACTS +
+ " AND (" + Clauses.CONTACT_IS_VISIBLE + ")=1 " + selection,
+ selectionArgs);
}
/**
@@ -2792,30 +3117,15 @@
}
}
- public void buildPhoneLookupAndRawContactQuery(SQLiteQueryBuilder qb, String number) {
- String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number);
- qb.setTables(Tables.DATA_JOIN_RAW_CONTACTS +
- " JOIN " + Tables.PHONE_LOOKUP
- + " ON(" + DataColumns.CONCRETE_ID + "=" + PhoneLookupColumns.DATA_ID + ")");
-
- StringBuilder sb = new StringBuilder();
- sb.append(PhoneLookupColumns.MIN_MATCH + "='");
- sb.append(minMatch);
- sb.append("' AND PHONE_NUMBERS_EQUAL(data." + Phone.NUMBER + ", ");
- DatabaseUtils.appendEscapedSQLString(sb, number);
- sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0)");
-
- qb.appendWhere(sb.toString());
- }
-
- public void buildPhoneLookupAndContactQuery(SQLiteQueryBuilder qb, String number) {
- String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number);
+ public void buildPhoneLookupAndContactQuery(
+ SQLiteQueryBuilder qb, String normalizedNumber, String numberE164) {
+ String minMatch = PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber);
StringBuilder sb = new StringBuilder();
appendPhoneLookupTables(sb, minMatch, true);
qb.setTables(sb.toString());
sb = new StringBuilder();
- appendPhoneLookupSelection(sb, number);
+ appendPhoneLookupSelection(sb, normalizedNumber, numberE164);
qb.appendWhere(sb.toString());
}
@@ -2825,7 +3135,7 @@
sb.append("(SELECT DISTINCT raw_contact_id" + " FROM ");
appendPhoneLookupTables(sb, minMatch, false);
sb.append(" WHERE ");
- appendPhoneLookupSelection(sb, number);
+ appendPhoneLookupSelection(sb, number, null);
sb.append(")");
return sb.toString();
}
@@ -2837,17 +3147,38 @@
sb.append(" JOIN " + getContactView() + " contacts_view"
+ " ON (contacts_view._id = raw_contacts.contact_id)");
}
- sb.append(", (SELECT data_id FROM phone_lookup "
- + "WHERE (" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.MIN_MATCH + " = '");
+ sb.append(", (SELECT data_id, normalized_number, length(normalized_number) as len "
+ + " FROM phone_lookup " + " WHERE (" + Tables.PHONE_LOOKUP + "."
+ + PhoneLookupColumns.MIN_MATCH + " = '");
sb.append(minMatch);
sb.append("')) AS lookup, " + Tables.DATA);
}
- private void appendPhoneLookupSelection(StringBuilder sb, String number) {
- sb.append("lookup.data_id=data._id AND data.raw_contact_id=raw_contacts._id"
- + " AND PHONE_NUMBERS_EQUAL(data." + Phone.NUMBER + ", ");
- DatabaseUtils.appendEscapedSQLString(sb, number);
- sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0)");
+ private void appendPhoneLookupSelection(StringBuilder sb, String number, String numberE164) {
+ sb.append("lookup.data_id=data._id AND data.raw_contact_id=raw_contacts._id");
+ boolean hasNumberE164 = !TextUtils.isEmpty(numberE164);
+ boolean hasNumber = !TextUtils.isEmpty(number);
+ if (hasNumberE164 || hasNumber) {
+ sb.append(" AND ( ");
+ if (hasNumberE164) {
+ sb.append(" lookup.normalized_number = ");
+ DatabaseUtils.appendEscapedSQLString(sb, numberE164);
+ }
+ if (hasNumberE164 && hasNumber) {
+ sb.append(" OR ");
+ }
+ if (hasNumber) {
+ int numberLen = number.length();
+ sb.append(" lookup.len <= ");
+ sb.append(numberLen);
+ sb.append(" AND substr(");
+ DatabaseUtils.appendEscapedSQLString(sb, number);
+ sb.append(',');
+ sb.append(numberLen);
+ sb.append(" - lookup.len + 1) = lookup.normalized_number");
+ }
+ sb.append(')');
+ }
}
public String getUseStrictPhoneNumberComparisonParameter() {
@@ -2970,10 +3301,14 @@
* Stores a key-value pair in the {@link Tables#PROPERTIES} table.
*/
public void setProperty(String key, String value) {
+ setProperty(getWritableDatabase(), key, value);
+ }
+
+ private void setProperty(SQLiteDatabase db, String key, String value) {
ContentValues values = new ContentValues();
values.put(PropertiesColumns.PROPERTY_KEY, key);
values.put(PropertiesColumns.PROPERTY_VALUE, value);
- getWritableDatabase().replace(Tables.PROPERTIES, null, values);
+ db.replace(Tables.PROPERTIES, null, values);
}
/**
@@ -2982,7 +3317,9 @@
*/
boolean hasAccessToRestrictedData() {
final PackageManager pm = mContext.getPackageManager();
- final String[] callerPackages = pm.getPackagesForUid(Binder.getCallingUid());
+ int caller = Binder.getCallingUid();
+ if (caller == 0) return true; // root can do anything
+ final String[] callerPackages = pm.getPackagesForUid(caller);
// Has restricted access if caller matches any packages
for (String callerPackage : callerPackages) {
@@ -3039,13 +3376,22 @@
return Views.GROUPS_ALL;
}
- public String getContactEntitiesView() {
- return getContactEntitiesView(false);
+ public String getRawEntitiesView() {
+ return getRawEntitiesView(false);
}
- public String getContactEntitiesView(boolean requireRestrictedView) {
+ public String getRawEntitiesView(boolean requireRestrictedView) {
return (hasAccessToRestrictedData() && !requireRestrictedView) ?
- Tables.CONTACT_ENTITIES : Tables.CONTACT_ENTITIES_RESTRICTED;
+ Views.RAW_ENTITIES : Views.RAW_ENTITIES_RESTRICTED;
+ }
+
+ public String getEntitiesView() {
+ return getEntitiesView(false);
+ }
+
+ public String getEntitiesView(boolean requireRestrictedView) {
+ return (hasAccessToRestrictedData() && !requireRestrictedView) ?
+ Views.ENTITIES : Views.ENTITIES_RESTRICTED;
}
/**
@@ -3123,4 +3469,10 @@
return sb.toString();
}
+
+ protected String getCountryIso() {
+ CountryDetector detector =
+ (CountryDetector) mContext.getSystemService(Context.COUNTRY_DETECTOR);
+ return detector.detectCountry().getCountryIso();
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 61a67d4..2120676 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -16,7 +16,8 @@
package com.android.providers.contacts;
-import com.android.internal.content.SyncStateContentProviderHelper;
+import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.providers.contacts.ContactAggregator.AggregationSuggestionParameter;
import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
@@ -35,6 +36,8 @@
import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
@@ -58,8 +61,6 @@
import android.content.SharedPreferences;
import android.content.SyncAdapterType;
import android.content.UriMatcher;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Configuration;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.database.CursorWrapper;
@@ -67,29 +68,36 @@
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.sqlite.SQLiteConstraintException;
-import android.database.sqlite.SQLiteContentHelper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
+import android.net.Uri.Builder;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.pim.vcard.VCardComposer;
-import android.pim.vcard.VCardConfig;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
-import android.provider.LiveFolders;
-import android.provider.OpenableColumns;
-import android.provider.SyncStateContract;
import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.ContactCounts;
import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.FullNameStyle;
import android.provider.ContactsContract.Groups;
@@ -101,16 +109,9 @@
import android.provider.ContactsContract.SearchSnippetColumns;
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
-import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.LiveFolders;
+import android.provider.OpenableColumns;
+import android.provider.SyncStateContract;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
@@ -147,7 +148,6 @@
/** Default for the maximum number of returned aggregation suggestions. */
private static final int DEFAULT_MAX_SUGGESTIONS = 5;
- private static final String GOOGLE_MY_CONTACTS_GROUP_TITLE = "System Group: My Contacts";
/**
* Property key for the legacy contact import version. The need for a version
* as opposed to a boolean flag is that if we discover bugs in the contact import process,
@@ -164,10 +164,10 @@
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- private static final String TIMES_CONTACED_SORT_COLUMN = "times_contacted_sort";
+ private static final String TIMES_CONTACTED_SORT_COLUMN = "times_contacted_sort";
private static final String STREQUENT_ORDER_BY = Contacts.STARRED + " DESC, "
- + TIMES_CONTACED_SORT_COLUMN + " DESC, "
+ + TIMES_CONTACTED_SORT_COLUMN + " DESC, "
+ Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
private static final String STREQUENT_LIMIT =
"(SELECT COUNT(1) FROM " + Tables.CONTACTS + " WHERE "
@@ -189,14 +189,19 @@
private static final int CONTACTS_ID = 1001;
private static final int CONTACTS_LOOKUP = 1002;
private static final int CONTACTS_LOOKUP_ID = 1003;
- private static final int CONTACTS_DATA = 1004;
+ private static final int CONTACTS_ID_DATA = 1004;
private static final int CONTACTS_FILTER = 1005;
private static final int CONTACTS_STREQUENT = 1006;
private static final int CONTACTS_STREQUENT_FILTER = 1007;
private static final int CONTACTS_GROUP = 1008;
- private static final int CONTACTS_PHOTO = 1009;
+ private static final int CONTACTS_ID_PHOTO = 1009;
private static final int CONTACTS_AS_VCARD = 1010;
private static final int CONTACTS_AS_MULTI_VCARD = 1011;
+ private static final int CONTACTS_LOOKUP_DATA = 1012;
+ private static final int CONTACTS_LOOKUP_ID_DATA = 1013;
+ private static final int CONTACTS_ID_ENTITIES = 1014;
+ private static final int CONTACTS_LOOKUP_ENTITIES = 1015;
+ private static final int CONTACTS_LOOKUP_ID_ENTITIES = 1016;
private static final int RAW_CONTACTS = 2002;
private static final int RAW_CONTACTS_ID = 2003;
@@ -246,6 +251,38 @@
private static final int PROVIDER_STATUS = 16001;
+ private static final int DIRECTORIES = 17001;
+ private static final int DIRECTORIES_ID = 17002;
+
+ private static final int COMPLETE_NAME = 18000;
+
+ private static final String SELECTION_FAVORITES_GROUPS_BY_RAW_CONTACT_ID =
+ RawContactsColumns.CONCRETE_ID + "=? AND "
+ + GroupsColumns.CONCRETE_ACCOUNT_NAME
+ + "=" + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " AND "
+ + GroupsColumns.CONCRETE_ACCOUNT_TYPE
+ + "=" + RawContactsColumns.CONCRETE_ACCOUNT_TYPE
+ + " AND " + Groups.FAVORITES + " != 0";
+
+ private static final String SELECTION_AUTO_ADD_GROUPS_BY_RAW_CONTACT_ID =
+ RawContactsColumns.CONCRETE_ID + "=? AND "
+ + GroupsColumns.CONCRETE_ACCOUNT_NAME + "="
+ + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " AND "
+ + GroupsColumns.CONCRETE_ACCOUNT_TYPE + "="
+ + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " AND "
+ + Groups.AUTO_ADD + " != 0";
+
+ private static final String[] PROJECTION_GROUP_ID
+ = new String[]{Tables.GROUPS + "." + Groups._ID};
+
+ private static final String SELECTION_GROUPMEMBERSHIP_DATA = DataColumns.MIMETYPE_ID + "=? "
+ + "AND " + GroupMembership.GROUP_ROW_ID + "=? "
+ + "AND " + GroupMembership.RAW_CONTACT_ID + "=?";
+
+ private static final String SELECTION_STARRED_FROM_RAW_CONTACTS =
+ "SELECT " + RawContacts.STARRED
+ + " FROM " + Tables.RAW_CONTACTS + " WHERE " + RawContacts._ID + "=?";
+
private interface DataContactsQuery {
public static final String TABLE = "data "
+ "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) "
@@ -340,6 +377,32 @@
" SET " + RawContacts.VERSION + " = " + RawContacts.VERSION + " + 1" +
" WHERE " + RawContacts._ID + " IN (";
+ // Current contacts - those contacted within the last 3 days (in seconds)
+ private static final long EMAIL_FILTER_CURRENT = 3 * 24 * 60 * 60;
+
+ // Recent contacts - those contacted within the last 30 days (in seconds)
+ private static final long EMAIL_FILTER_RECENT = 30 * 24 * 60 * 60;
+
+ private static final String TIME_SINCE_LAST_CONTACTED =
+ "(strftime('%s', 'now') - " + Contacts.LAST_TIME_CONTACTED + "/1000)";
+
+ /*
+ * Sorting order for email address suggestions: first starred, then the rest.
+ * Within the starred/unstarred groups - three buckets: very recently contacted, then fairly
+ * recently contacted, then the rest. Within each of the bucket - descending count
+ * of times contacted. If all else fails, alphabetical. (Super)primary email
+ * address is returned before other addresses for the same contact.
+ */
+ private static final String EMAIL_FILTER_SORT_ORDER =
+ "(CASE WHEN " + Contacts.STARRED + "=1 THEN 0 ELSE 1 END), "
+ + "(CASE WHEN " + TIME_SINCE_LAST_CONTACTED + " < " + EMAIL_FILTER_CURRENT + " THEN 0 "
+ + " WHEN " + TIME_SINCE_LAST_CONTACTED + " < " + EMAIL_FILTER_RECENT + " THEN 1 "
+ + " ELSE 2 END),"
+ + Contacts.TIMES_CONTACTED + " DESC, "
+ + Contacts.DISPLAY_NAME + ", "
+ + Data.CONTACT_ID + ", "
+ + Data.IS_SUPER_PRIMARY + " DESC";
+
/** Name lookup types used for contact filtering */
private static final String CONTACT_LOOKUP_NAME_TYPES =
NameLookupType.NAME_COLLATION_KEY + "," +
@@ -349,41 +412,405 @@
NameLookupType.ORGANIZATION + "," +
NameLookupType.NAME_CONSONANTS;
+ /**
+ * If any of these columns are used in a Data projection, there is no point in
+ * using the DISTINCT keyword, which can negatively affect performance.
+ */
+ private static final String[] DISTINCT_DATA_PROHIBITING_COLUMNS = {
+ Data._ID,
+ Data.RAW_CONTACT_ID,
+ Data.NAME_RAW_CONTACT_ID,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.DIRTY,
+ RawContacts.NAME_VERIFIED,
+ RawContacts.SOURCE_ID,
+ RawContacts.VERSION,
+ };
+
+ private static final ProjectionMap sContactsColumns = ProjectionMap.builder()
+ .add(Contacts.CUSTOM_RINGTONE)
+ .add(Contacts.DISPLAY_NAME)
+ .add(Contacts.DISPLAY_NAME_ALTERNATIVE)
+ .add(Contacts.DISPLAY_NAME_SOURCE)
+ .add(Contacts.IN_VISIBLE_GROUP)
+ .add(Contacts.LAST_TIME_CONTACTED)
+ .add(Contacts.LOOKUP_KEY)
+ .add(Contacts.PHONETIC_NAME)
+ .add(Contacts.PHONETIC_NAME_STYLE)
+ .add(Contacts.PHOTO_ID)
+ .add(Contacts.PHOTO_URI)
+ .add(Contacts.PHOTO_THUMBNAIL_URI)
+ .add(Contacts.SEND_TO_VOICEMAIL)
+ .add(Contacts.SORT_KEY_ALTERNATIVE)
+ .add(Contacts.SORT_KEY_PRIMARY)
+ .add(Contacts.STARRED)
+ .add(Contacts.TIMES_CONTACTED)
+ .add(Contacts.HAS_PHONE_NUMBER)
+ .build();
+
+ private static final ProjectionMap sContactsPresenceColumns = ProjectionMap.builder()
+ .add(Contacts.CONTACT_PRESENCE,
+ Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE)
+ .add(Contacts.CONTACT_CHAT_CAPABILITY,
+ Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY)
+ .add(Contacts.CONTACT_STATUS,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS)
+ .add(Contacts.CONTACT_STATUS_TIMESTAMP,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP)
+ .add(Contacts.CONTACT_STATUS_RES_PACKAGE,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE)
+ .add(Contacts.CONTACT_STATUS_LABEL,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL)
+ .add(Contacts.CONTACT_STATUS_ICON,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON)
+ .build();
+
+ private static final ProjectionMap sSnippetColumns = ProjectionMap.builder()
+ .add(SearchSnippetColumns.SNIPPET_MIMETYPE)
+ .add(SearchSnippetColumns.SNIPPET_DATA_ID)
+ .add(SearchSnippetColumns.SNIPPET_DATA1)
+ .add(SearchSnippetColumns.SNIPPET_DATA2)
+ .add(SearchSnippetColumns.SNIPPET_DATA3)
+ .add(SearchSnippetColumns.SNIPPET_DATA4)
+ .build();
+
+
+ private static final ProjectionMap sRawContactColumns = ProjectionMap.builder()
+ .add(RawContacts.ACCOUNT_NAME)
+ .add(RawContacts.ACCOUNT_TYPE)
+ .add(RawContacts.DIRTY)
+ .add(RawContacts.NAME_VERIFIED)
+ .add(RawContacts.SOURCE_ID)
+ .add(RawContacts.VERSION)
+ .build();
+
+ private static final ProjectionMap sRawContactSyncColumns = ProjectionMap.builder()
+ .add(RawContacts.SYNC1)
+ .add(RawContacts.SYNC2)
+ .add(RawContacts.SYNC3)
+ .add(RawContacts.SYNC4)
+ .build();
+
+ private static final ProjectionMap sDataColumns = ProjectionMap.builder()
+ .add(Data.DATA1)
+ .add(Data.DATA2)
+ .add(Data.DATA3)
+ .add(Data.DATA4)
+ .add(Data.DATA5)
+ .add(Data.DATA6)
+ .add(Data.DATA7)
+ .add(Data.DATA8)
+ .add(Data.DATA9)
+ .add(Data.DATA10)
+ .add(Data.DATA11)
+ .add(Data.DATA12)
+ .add(Data.DATA13)
+ .add(Data.DATA14)
+ .add(Data.DATA15)
+ .add(Data.DATA_VERSION)
+ .add(Data.IS_PRIMARY)
+ .add(Data.IS_SUPER_PRIMARY)
+ .add(Data.MIMETYPE)
+ .add(Data.RES_PACKAGE)
+ .add(Data.SYNC1)
+ .add(Data.SYNC2)
+ .add(Data.SYNC3)
+ .add(Data.SYNC4)
+ .add(GroupMembership.GROUP_SOURCE_ID)
+ .build();
+
+ private static final ProjectionMap sContactPresenceColumns = ProjectionMap.builder()
+ .add(Contacts.CONTACT_PRESENCE,
+ Tables.AGGREGATED_PRESENCE + '.' + StatusUpdates.PRESENCE)
+ .add(Contacts.CONTACT_CHAT_CAPABILITY,
+ Tables.AGGREGATED_PRESENCE + '.' + StatusUpdates.CHAT_CAPABILITY)
+ .add(Contacts.CONTACT_STATUS,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS)
+ .add(Contacts.CONTACT_STATUS_TIMESTAMP,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP)
+ .add(Contacts.CONTACT_STATUS_RES_PACKAGE,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE)
+ .add(Contacts.CONTACT_STATUS_LABEL,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL)
+ .add(Contacts.CONTACT_STATUS_ICON,
+ ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON)
+ .build();
+
+ private static final ProjectionMap sDataPresenceColumns = ProjectionMap.builder()
+ .add(Data.PRESENCE, Tables.PRESENCE + "." + StatusUpdates.PRESENCE)
+ .add(Data.CHAT_CAPABILITY, Tables.PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY)
+ .add(Data.STATUS, StatusUpdatesColumns.CONCRETE_STATUS)
+ .add(Data.STATUS_TIMESTAMP, StatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP)
+ .add(Data.STATUS_RES_PACKAGE, StatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE)
+ .add(Data.STATUS_LABEL, StatusUpdatesColumns.CONCRETE_STATUS_LABEL)
+ .add(Data.STATUS_ICON, StatusUpdatesColumns.CONCRETE_STATUS_ICON)
+ .build();
/** Contains just BaseColumns._COUNT */
- private static final HashMap<String, String> sCountProjectionMap;
+ private static final ProjectionMap sCountProjectionMap = ProjectionMap.builder()
+ .add(BaseColumns._COUNT, "COUNT(*)")
+ .build();
+
/** Contains just the contacts columns */
- private static final HashMap<String, String> sContactsProjectionMap;
+ private static final ProjectionMap sContactsProjectionMap = ProjectionMap.builder()
+ .add(Contacts._ID)
+ .add(Contacts.HAS_PHONE_NUMBER)
+ .add(Contacts.NAME_RAW_CONTACT_ID)
+ .addAll(sContactsColumns)
+ .addAll(sContactsPresenceColumns)
+ .build();
+
/** Contains just the contacts columns */
- private static final HashMap<String, String> sContactsProjectionWithSnippetMap;
+ private static final ProjectionMap sContactsProjectionWithSnippetMap = ProjectionMap.builder()
+ .addAll(sContactsProjectionMap)
+ .addAll(sSnippetColumns)
+ .build();
/** Used for pushing starred contacts to the top of a times contacted list **/
- private static final HashMap<String, String> sStrequentStarredProjectionMap;
- private static final HashMap<String, String> sStrequentFrequentProjectionMap;
+ private static final ProjectionMap sStrequentStarredProjectionMap = ProjectionMap.builder()
+ .addAll(sContactsProjectionMap)
+ .add(TIMES_CONTACTED_SORT_COLUMN, String.valueOf(Long.MAX_VALUE))
+ .build();
+
+ private static final ProjectionMap sStrequentFrequentProjectionMap = ProjectionMap.builder()
+ .addAll(sContactsProjectionMap)
+ .add(TIMES_CONTACTED_SORT_COLUMN, Contacts.TIMES_CONTACTED)
+ .build();
+
/** Contains just the contacts vCard columns */
- private static final HashMap<String, String> sContactsVCardProjectionMap;
+ private static final ProjectionMap sContactsVCardProjectionMap = ProjectionMap.builder()
+ .add(OpenableColumns.DISPLAY_NAME, Contacts.DISPLAY_NAME + " || '.vcf'")
+ .add(OpenableColumns.SIZE, "NULL")
+ .build();
+
/** Contains just the raw contacts columns */
- private static final HashMap<String, String> sRawContactsProjectionMap;
- /** Contains the columns from the raw contacts entity view*/
- private static final HashMap<String, String> sRawContactsEntityProjectionMap;
+ private static final ProjectionMap sRawContactsProjectionMap = ProjectionMap.builder()
+ .add(RawContacts._ID)
+ .add(RawContacts.CONTACT_ID)
+ .add(RawContacts.DELETED)
+ .add(RawContacts.DISPLAY_NAME_PRIMARY)
+ .add(RawContacts.DISPLAY_NAME_ALTERNATIVE)
+ .add(RawContacts.DISPLAY_NAME_SOURCE)
+ .add(RawContacts.PHONETIC_NAME)
+ .add(RawContacts.PHONETIC_NAME_STYLE)
+ .add(RawContacts.SORT_KEY_PRIMARY)
+ .add(RawContacts.SORT_KEY_ALTERNATIVE)
+ .add(RawContacts.TIMES_CONTACTED)
+ .add(RawContacts.LAST_TIME_CONTACTED)
+ .add(RawContacts.CUSTOM_RINGTONE)
+ .add(RawContacts.SEND_TO_VOICEMAIL)
+ .add(RawContacts.STARRED)
+ .add(RawContacts.AGGREGATION_MODE)
+ .addAll(sRawContactColumns)
+ .addAll(sRawContactSyncColumns)
+ .build();
+
+ /** Contains the columns from the raw entity view*/
+ private static final ProjectionMap sRawEntityProjectionMap = ProjectionMap.builder()
+ .add(RawContacts._ID)
+ .add(RawContacts.CONTACT_ID)
+ .add(RawContacts.Entity.DATA_ID)
+ .add(RawContacts.IS_RESTRICTED)
+ .add(RawContacts.DELETED)
+ .add(RawContacts.STARRED)
+ .addAll(sRawContactColumns)
+ .addAll(sRawContactSyncColumns)
+ .addAll(sDataColumns)
+ .build();
+
+ /** Contains the columns from the contact entity view*/
+ private static final ProjectionMap sEntityProjectionMap = ProjectionMap.builder()
+ .add(Contacts.Entity._ID)
+ .add(Contacts.Entity.CONTACT_ID)
+ .add(Contacts.Entity.RAW_CONTACT_ID)
+ .add(Contacts.Entity.DATA_ID)
+ .add(Contacts.Entity.NAME_RAW_CONTACT_ID)
+ .add(Contacts.Entity.DELETED)
+ .add(Contacts.Entity.IS_RESTRICTED)
+ .addAll(sContactsColumns)
+ .addAll(sContactPresenceColumns)
+ .addAll(sRawContactColumns)
+ .addAll(sRawContactSyncColumns)
+ .addAll(sDataColumns)
+ .addAll(sDataPresenceColumns)
+ .build();
+
/** Contains columns from the data view */
- private static final HashMap<String, String> sDataProjectionMap;
+ private static final ProjectionMap sDataProjectionMap = ProjectionMap.builder()
+ .add(Data._ID)
+ .add(Data.RAW_CONTACT_ID)
+ .add(Data.CONTACT_ID)
+ .add(Data.NAME_RAW_CONTACT_ID)
+ .addAll(sDataColumns)
+ .addAll(sDataPresenceColumns)
+ .addAll(sRawContactColumns)
+ .addAll(sContactsColumns)
+ .addAll(sContactPresenceColumns)
+ .build();
+
/** Contains columns from the data view */
- private static final HashMap<String, String> sDistinctDataProjectionMap;
+ private static final ProjectionMap sDistinctDataProjectionMap = ProjectionMap.builder()
+ .add(Data._ID, "MIN(" + Data._ID + ")")
+ .add(RawContacts.CONTACT_ID)
+ .addAll(sDataColumns)
+ .addAll(sDataPresenceColumns)
+ .addAll(sContactsColumns)
+ .addAll(sContactPresenceColumns)
+ .build();
+
/** Contains the data and contacts columns, for joined tables */
- private static final HashMap<String, String> sPhoneLookupProjectionMap;
+ private static final ProjectionMap sPhoneLookupProjectionMap = ProjectionMap.builder()
+ .add(PhoneLookup._ID, "contacts_view." + Contacts._ID)
+ .add(PhoneLookup.LOOKUP_KEY, "contacts_view." + Contacts.LOOKUP_KEY)
+ .add(PhoneLookup.DISPLAY_NAME, "contacts_view." + Contacts.DISPLAY_NAME)
+ .add(PhoneLookup.LAST_TIME_CONTACTED, "contacts_view." + Contacts.LAST_TIME_CONTACTED)
+ .add(PhoneLookup.TIMES_CONTACTED, "contacts_view." + Contacts.TIMES_CONTACTED)
+ .add(PhoneLookup.STARRED, "contacts_view." + Contacts.STARRED)
+ .add(PhoneLookup.IN_VISIBLE_GROUP, "contacts_view." + Contacts.IN_VISIBLE_GROUP)
+ .add(PhoneLookup.PHOTO_ID, "contacts_view." + Contacts.PHOTO_ID)
+ .add(PhoneLookup.PHOTO_URI, "contacts_view." + Contacts.PHOTO_URI)
+ .add(PhoneLookup.PHOTO_THUMBNAIL_URI, "contacts_view." + Contacts.PHOTO_THUMBNAIL_URI)
+ .add(PhoneLookup.CUSTOM_RINGTONE, "contacts_view." + Contacts.CUSTOM_RINGTONE)
+ .add(PhoneLookup.HAS_PHONE_NUMBER, "contacts_view." + Contacts.HAS_PHONE_NUMBER)
+ .add(PhoneLookup.SEND_TO_VOICEMAIL, "contacts_view." + Contacts.SEND_TO_VOICEMAIL)
+ .add(PhoneLookup.NUMBER, Phone.NUMBER)
+ .add(PhoneLookup.TYPE, Phone.TYPE)
+ .add(PhoneLookup.LABEL, Phone.LABEL)
+ .add(PhoneLookup.NORMALIZED_NUMBER, Phone.NORMALIZED_NUMBER)
+ .build();
+
/** Contains the just the {@link Groups} columns */
- private static final HashMap<String, String> sGroupsProjectionMap;
+ private static final ProjectionMap sGroupsProjectionMap = ProjectionMap.builder()
+ .add(Groups._ID)
+ .add(Groups.ACCOUNT_NAME)
+ .add(Groups.ACCOUNT_TYPE)
+ .add(Groups.SOURCE_ID)
+ .add(Groups.DIRTY)
+ .add(Groups.VERSION)
+ .add(Groups.RES_PACKAGE)
+ .add(Groups.TITLE)
+ .add(Groups.TITLE_RES)
+ .add(Groups.GROUP_VISIBLE)
+ .add(Groups.SYSTEM_ID)
+ .add(Groups.DELETED)
+ .add(Groups.NOTES)
+ .add(Groups.SHOULD_SYNC)
+ .add(Groups.FAVORITES)
+ .add(Groups.AUTO_ADD)
+ .add(Groups.SYNC1)
+ .add(Groups.SYNC2)
+ .add(Groups.SYNC3)
+ .add(Groups.SYNC4)
+ .build();
+
/** Contains {@link Groups} columns along with summary details */
- private static final HashMap<String, String> sGroupsSummaryProjectionMap;
+ private static final ProjectionMap sGroupsSummaryProjectionMap = ProjectionMap.builder()
+ .addAll(sGroupsProjectionMap)
+ .add(Groups.SUMMARY_COUNT,
+ "(SELECT COUNT(DISTINCT " + ContactsColumns.CONCRETE_ID
+ + ") FROM " + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS
+ + " WHERE " + Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP
+ + " AND " + Clauses.BELONGS_TO_GROUP
+ + ")")
+ .add(Groups.SUMMARY_WITH_PHONES,
+ "(SELECT COUNT(DISTINCT " + ContactsColumns.CONCRETE_ID
+ + ") FROM " + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS
+ + " WHERE " + Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP
+ + " AND " + Clauses.BELONGS_TO_GROUP
+ + " AND " + Contacts.HAS_PHONE_NUMBER + ")")
+ .build();
+
/** Contains the agg_exceptions columns */
- private static final HashMap<String, String> sAggregationExceptionsProjectionMap;
+ private static final ProjectionMap sAggregationExceptionsProjectionMap = ProjectionMap.builder()
+ .add(AggregationExceptionColumns._ID, Tables.AGGREGATION_EXCEPTIONS + "._id")
+ .add(AggregationExceptions.TYPE)
+ .add(AggregationExceptions.RAW_CONTACT_ID1)
+ .add(AggregationExceptions.RAW_CONTACT_ID2)
+ .build();
+
/** Contains the agg_exceptions columns */
- private static final HashMap<String, String> sSettingsProjectionMap;
+ private static final ProjectionMap sSettingsProjectionMap = ProjectionMap.builder()
+ .add(Settings.ACCOUNT_NAME)
+ .add(Settings.ACCOUNT_TYPE)
+ .add(Settings.UNGROUPED_VISIBLE)
+ .add(Settings.SHOULD_SYNC)
+ .add(Settings.ANY_UNSYNCED,
+ "(CASE WHEN MIN(" + Settings.SHOULD_SYNC
+ + ",(SELECT "
+ + "(CASE WHEN MIN(" + Groups.SHOULD_SYNC + ") IS NULL"
+ + " THEN 1"
+ + " ELSE MIN(" + Groups.SHOULD_SYNC + ")"
+ + " END)"
+ + " FROM " + Tables.GROUPS
+ + " WHERE " + GroupsColumns.CONCRETE_ACCOUNT_NAME + "="
+ + SettingsColumns.CONCRETE_ACCOUNT_NAME
+ + " AND " + GroupsColumns.CONCRETE_ACCOUNT_TYPE + "="
+ + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "))=0"
+ + " THEN 1"
+ + " ELSE 0"
+ + " END)")
+ .add(Settings.UNGROUPED_COUNT,
+ "(SELECT COUNT(*)"
+ + " FROM (SELECT 1"
+ + " FROM " + Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS
+ + " GROUP BY " + Clauses.GROUP_BY_ACCOUNT_CONTACT_ID
+ + " HAVING " + Clauses.HAVING_NO_GROUPS
+ + "))")
+ .add(Settings.UNGROUPED_WITH_PHONES,
+ "(SELECT COUNT(*)"
+ + " FROM (SELECT 1"
+ + " FROM " + Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS
+ + " WHERE " + Contacts.HAS_PHONE_NUMBER
+ + " GROUP BY " + Clauses.GROUP_BY_ACCOUNT_CONTACT_ID
+ + " HAVING " + Clauses.HAVING_NO_GROUPS
+ + "))")
+ .build();
+
/** Contains StatusUpdates columns */
- private static final HashMap<String, String> sStatusUpdatesProjectionMap;
+ private static final ProjectionMap sStatusUpdatesProjectionMap = ProjectionMap.builder()
+ .add(PresenceColumns.RAW_CONTACT_ID)
+ .add(StatusUpdates.DATA_ID, DataColumns.CONCRETE_ID)
+ .add(StatusUpdates.IM_ACCOUNT)
+ .add(StatusUpdates.IM_HANDLE)
+ .add(StatusUpdates.PROTOCOL)
+ // We cannot allow a null in the custom protocol field, because SQLite3 does not
+ // properly enforce uniqueness of null values
+ .add(StatusUpdates.CUSTOM_PROTOCOL,
+ "(CASE WHEN " + StatusUpdates.CUSTOM_PROTOCOL + "=''"
+ + " THEN NULL"
+ + " ELSE " + StatusUpdates.CUSTOM_PROTOCOL + " END)")
+ .add(StatusUpdates.PRESENCE)
+ .add(StatusUpdates.CHAT_CAPABILITY)
+ .add(StatusUpdates.STATUS)
+ .add(StatusUpdates.STATUS_TIMESTAMP)
+ .add(StatusUpdates.STATUS_RES_PACKAGE)
+ .add(StatusUpdates.STATUS_ICON)
+ .add(StatusUpdates.STATUS_LABEL)
+ .build();
+
/** Contains Live Folders columns */
- private static final HashMap<String, String> sLiveFoldersProjectionMap;
+ private static final ProjectionMap sLiveFoldersProjectionMap = ProjectionMap.builder()
+ .add(LiveFolders._ID, Contacts._ID)
+ .add(LiveFolders.NAME, Contacts.DISPLAY_NAME)
+ // TODO: Put contact photo back when we have a way to display a default icon
+ // for contacts without a photo
+ // .add(LiveFolders.ICON_BITMAP, Photos.DATA)
+ .build();
+
+ /** Contains {@link Directory} columns */
+ private static final ProjectionMap sDirectoryProjectionMap = ProjectionMap.builder()
+ .add(Directory._ID)
+ .add(Directory.PACKAGE_NAME)
+ .add(Directory.TYPE_RESOURCE_ID)
+ .add(Directory.DISPLAY_NAME)
+ .add(Directory.DIRECTORY_AUTHORITY)
+ .add(Directory.ACCOUNT_TYPE)
+ .add(Directory.ACCOUNT_NAME)
+ .add(Directory.EXPORT_SUPPORT)
+ .add(Directory.SHORTCUT_SUPPORT)
+ .add(Directory.PHOTO_SUPPORT)
+ .build();
// where clause to update the status_updates table
private static final String WHERE_CLAUSE_FOR_STATUS_UPDATES_TABLE =
@@ -433,15 +860,24 @@
final UriMatcher matcher = sUriMatcher;
matcher.addURI(ContactsContract.AUTHORITY, "contacts", CONTACTS);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
- matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/data", CONTACTS_DATA);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/data", CONTACTS_ID_DATA);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/entities", CONTACTS_ID_ENTITIES);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/suggestions",
AGGREGATION_SUGGESTIONS);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/suggestions/*",
AGGREGATION_SUGGESTIONS);
- matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", CONTACTS_PHOTO);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", CONTACTS_ID_PHOTO);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter", CONTACTS_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter/*", CONTACTS_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", CONTACTS_LOOKUP);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/data", CONTACTS_LOOKUP_DATA);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", CONTACTS_LOOKUP_ID);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#/data",
+ CONTACTS_LOOKUP_ID_DATA);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/entities",
+ CONTACTS_LOOKUP_ENTITIES);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#/entities",
+ CONTACTS_LOOKUP_ID_ENTITIES);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/as_vcard/*", CONTACTS_AS_VCARD);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/as_multi_vcard/*",
CONTACTS_AS_MULTI_VCARD);
@@ -465,6 +901,7 @@
matcher.addURI(ContactsContract.AUTHORITY, "data/phones/filter/*", PHONES_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails", EMAILS);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/#", EMAILS_ID);
+ matcher.addURI(ContactsContract.AUTHORITY, "data/emails/lookup", EMAILS_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/lookup/*", EMAILS_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/filter", EMAILS_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/filter/*", EMAILS_FILTER);
@@ -507,469 +944,24 @@
LIVE_FOLDERS_CONTACTS_FAVORITES);
matcher.addURI(ContactsContract.AUTHORITY, "provider_status", PROVIDER_STATUS);
+
+ matcher.addURI(ContactsContract.AUTHORITY, "directories", DIRECTORIES);
+ matcher.addURI(ContactsContract.AUTHORITY, "directories/#", DIRECTORIES_ID);
+
+ matcher.addURI(ContactsContract.AUTHORITY, "complete_name", COMPLETE_NAME);
}
- static {
- sCountProjectionMap = new HashMap<String, String>();
- sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
-
- sContactsProjectionMap = new HashMap<String, String>();
- sContactsProjectionMap.put(Contacts._ID, Contacts._ID);
- sContactsProjectionMap.put(Contacts.DISPLAY_NAME, Contacts.DISPLAY_NAME_PRIMARY);
- sContactsProjectionMap.put(Contacts.DISPLAY_NAME_ALTERNATIVE,
- Contacts.DISPLAY_NAME_ALTERNATIVE);
- sContactsProjectionMap.put(Contacts.DISPLAY_NAME_SOURCE, Contacts.DISPLAY_NAME_SOURCE);
- sContactsProjectionMap.put(Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME);
- sContactsProjectionMap.put(Contacts.PHONETIC_NAME_STYLE, Contacts.PHONETIC_NAME_STYLE);
- sContactsProjectionMap.put(Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_PRIMARY);
- sContactsProjectionMap.put(Contacts.SORT_KEY_ALTERNATIVE, Contacts.SORT_KEY_ALTERNATIVE);
- sContactsProjectionMap.put(Contacts.LAST_TIME_CONTACTED, Contacts.LAST_TIME_CONTACTED);
- sContactsProjectionMap.put(Contacts.TIMES_CONTACTED, Contacts.TIMES_CONTACTED);
- sContactsProjectionMap.put(Contacts.STARRED, Contacts.STARRED);
- sContactsProjectionMap.put(Contacts.IN_VISIBLE_GROUP, Contacts.IN_VISIBLE_GROUP);
- sContactsProjectionMap.put(Contacts.PHOTO_ID, Contacts.PHOTO_ID);
- sContactsProjectionMap.put(Contacts.CUSTOM_RINGTONE, Contacts.CUSTOM_RINGTONE);
- sContactsProjectionMap.put(Contacts.HAS_PHONE_NUMBER, Contacts.HAS_PHONE_NUMBER);
- sContactsProjectionMap.put(Contacts.SEND_TO_VOICEMAIL, Contacts.SEND_TO_VOICEMAIL);
- sContactsProjectionMap.put(Contacts.LOOKUP_KEY, Contacts.LOOKUP_KEY);
-
- // Handle projections for Contacts-level statuses
- addProjection(sContactsProjectionMap, Contacts.CONTACT_PRESENCE,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_CHAT_CAPABILITY,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_TIMESTAMP,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_RES_PACKAGE,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_LABEL,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_ICON,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON);
-
- sContactsProjectionWithSnippetMap = new HashMap<String, String>();
- sContactsProjectionWithSnippetMap.putAll(sContactsProjectionMap);
- sContactsProjectionWithSnippetMap.put(SearchSnippetColumns.SNIPPET_MIMETYPE,
- SearchSnippetColumns.SNIPPET_MIMETYPE);
- sContactsProjectionWithSnippetMap.put(SearchSnippetColumns.SNIPPET_DATA_ID,
- SearchSnippetColumns.SNIPPET_DATA_ID);
- sContactsProjectionWithSnippetMap.put(SearchSnippetColumns.SNIPPET_DATA1,
- SearchSnippetColumns.SNIPPET_DATA1);
- sContactsProjectionWithSnippetMap.put(SearchSnippetColumns.SNIPPET_DATA2,
- SearchSnippetColumns.SNIPPET_DATA2);
- sContactsProjectionWithSnippetMap.put(SearchSnippetColumns.SNIPPET_DATA3,
- SearchSnippetColumns.SNIPPET_DATA3);
- sContactsProjectionWithSnippetMap.put(SearchSnippetColumns.SNIPPET_DATA4,
- SearchSnippetColumns.SNIPPET_DATA4);
-
- sStrequentStarredProjectionMap = new HashMap<String, String>(sContactsProjectionMap);
- sStrequentStarredProjectionMap.put(TIMES_CONTACED_SORT_COLUMN,
- Long.MAX_VALUE + " AS " + TIMES_CONTACED_SORT_COLUMN);
-
- sStrequentFrequentProjectionMap = new HashMap<String, String>(sContactsProjectionMap);
- sStrequentFrequentProjectionMap.put(TIMES_CONTACED_SORT_COLUMN,
- Contacts.TIMES_CONTACTED + " AS " + TIMES_CONTACED_SORT_COLUMN);
-
- sContactsVCardProjectionMap = Maps.newHashMap();
- sContactsVCardProjectionMap.put(OpenableColumns.DISPLAY_NAME, Contacts.DISPLAY_NAME
- + " || '.vcf' AS " + OpenableColumns.DISPLAY_NAME);
- sContactsVCardProjectionMap.put(OpenableColumns.SIZE, "NULL AS " + OpenableColumns.SIZE);
-
- sRawContactsProjectionMap = new HashMap<String, String>();
- sRawContactsProjectionMap.put(RawContacts._ID, RawContacts._ID);
- sRawContactsProjectionMap.put(RawContacts.CONTACT_ID, RawContacts.CONTACT_ID);
- sRawContactsProjectionMap.put(RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_NAME);
- sRawContactsProjectionMap.put(RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_TYPE);
- sRawContactsProjectionMap.put(RawContacts.SOURCE_ID, RawContacts.SOURCE_ID);
- sRawContactsProjectionMap.put(RawContacts.VERSION, RawContacts.VERSION);
- sRawContactsProjectionMap.put(RawContacts.DIRTY, RawContacts.DIRTY);
- sRawContactsProjectionMap.put(RawContacts.DELETED, RawContacts.DELETED);
- sRawContactsProjectionMap.put(RawContacts.DISPLAY_NAME_PRIMARY,
- RawContacts.DISPLAY_NAME_PRIMARY);
- sRawContactsProjectionMap.put(RawContacts.DISPLAY_NAME_ALTERNATIVE,
- RawContacts.DISPLAY_NAME_ALTERNATIVE);
- sRawContactsProjectionMap.put(RawContacts.DISPLAY_NAME_SOURCE,
- RawContacts.DISPLAY_NAME_SOURCE);
- sRawContactsProjectionMap.put(RawContacts.PHONETIC_NAME,
- RawContacts.PHONETIC_NAME);
- sRawContactsProjectionMap.put(RawContacts.PHONETIC_NAME_STYLE,
- RawContacts.PHONETIC_NAME_STYLE);
- sRawContactsProjectionMap.put(RawContacts.NAME_VERIFIED,
- RawContacts.NAME_VERIFIED);
- sRawContactsProjectionMap.put(RawContacts.SORT_KEY_PRIMARY,
- RawContacts.SORT_KEY_PRIMARY);
- sRawContactsProjectionMap.put(RawContacts.SORT_KEY_ALTERNATIVE,
- RawContacts.SORT_KEY_ALTERNATIVE);
- sRawContactsProjectionMap.put(RawContacts.TIMES_CONTACTED, RawContacts.TIMES_CONTACTED);
- sRawContactsProjectionMap.put(RawContacts.LAST_TIME_CONTACTED,
- RawContacts.LAST_TIME_CONTACTED);
- sRawContactsProjectionMap.put(RawContacts.CUSTOM_RINGTONE, RawContacts.CUSTOM_RINGTONE);
- sRawContactsProjectionMap.put(RawContacts.SEND_TO_VOICEMAIL, RawContacts.SEND_TO_VOICEMAIL);
- sRawContactsProjectionMap.put(RawContacts.STARRED, RawContacts.STARRED);
- sRawContactsProjectionMap.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE);
- sRawContactsProjectionMap.put(RawContacts.SYNC1, RawContacts.SYNC1);
- sRawContactsProjectionMap.put(RawContacts.SYNC2, RawContacts.SYNC2);
- sRawContactsProjectionMap.put(RawContacts.SYNC3, RawContacts.SYNC3);
- sRawContactsProjectionMap.put(RawContacts.SYNC4, RawContacts.SYNC4);
-
- sDataProjectionMap = new HashMap<String, String>();
- sDataProjectionMap.put(Data._ID, Data._ID);
- sDataProjectionMap.put(Data.RAW_CONTACT_ID, Data.RAW_CONTACT_ID);
- sDataProjectionMap.put(Data.DATA_VERSION, Data.DATA_VERSION);
- sDataProjectionMap.put(Data.IS_PRIMARY, Data.IS_PRIMARY);
- sDataProjectionMap.put(Data.IS_SUPER_PRIMARY, Data.IS_SUPER_PRIMARY);
- sDataProjectionMap.put(Data.RES_PACKAGE, Data.RES_PACKAGE);
- sDataProjectionMap.put(Data.MIMETYPE, Data.MIMETYPE);
- sDataProjectionMap.put(Data.DATA1, Data.DATA1);
- sDataProjectionMap.put(Data.DATA2, Data.DATA2);
- sDataProjectionMap.put(Data.DATA3, Data.DATA3);
- sDataProjectionMap.put(Data.DATA4, Data.DATA4);
- sDataProjectionMap.put(Data.DATA5, Data.DATA5);
- sDataProjectionMap.put(Data.DATA6, Data.DATA6);
- sDataProjectionMap.put(Data.DATA7, Data.DATA7);
- sDataProjectionMap.put(Data.DATA8, Data.DATA8);
- sDataProjectionMap.put(Data.DATA9, Data.DATA9);
- sDataProjectionMap.put(Data.DATA10, Data.DATA10);
- sDataProjectionMap.put(Data.DATA11, Data.DATA11);
- sDataProjectionMap.put(Data.DATA12, Data.DATA12);
- sDataProjectionMap.put(Data.DATA13, Data.DATA13);
- sDataProjectionMap.put(Data.DATA14, Data.DATA14);
- sDataProjectionMap.put(Data.DATA15, Data.DATA15);
- sDataProjectionMap.put(Data.SYNC1, Data.SYNC1);
- sDataProjectionMap.put(Data.SYNC2, Data.SYNC2);
- sDataProjectionMap.put(Data.SYNC3, Data.SYNC3);
- sDataProjectionMap.put(Data.SYNC4, Data.SYNC4);
- sDataProjectionMap.put(Data.CONTACT_ID, Data.CONTACT_ID);
- sDataProjectionMap.put(RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_NAME);
- sDataProjectionMap.put(RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_TYPE);
- sDataProjectionMap.put(RawContacts.SOURCE_ID, RawContacts.SOURCE_ID);
- sDataProjectionMap.put(RawContacts.VERSION, RawContacts.VERSION);
- sDataProjectionMap.put(RawContacts.DIRTY, RawContacts.DIRTY);
- sDataProjectionMap.put(RawContacts.NAME_VERIFIED, RawContacts.NAME_VERIFIED);
- sDataProjectionMap.put(Contacts.LOOKUP_KEY, Contacts.LOOKUP_KEY);
- sDataProjectionMap.put(Contacts.DISPLAY_NAME, Contacts.DISPLAY_NAME);
- sDataProjectionMap.put(Contacts.DISPLAY_NAME_ALTERNATIVE,
- Contacts.DISPLAY_NAME_ALTERNATIVE);
- sDataProjectionMap.put(Contacts.DISPLAY_NAME_SOURCE, Contacts.DISPLAY_NAME_SOURCE);
- sDataProjectionMap.put(Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME);
- sDataProjectionMap.put(Contacts.PHONETIC_NAME_STYLE, Contacts.PHONETIC_NAME_STYLE);
- sDataProjectionMap.put(Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_PRIMARY);
- sDataProjectionMap.put(Contacts.SORT_KEY_ALTERNATIVE, Contacts.SORT_KEY_ALTERNATIVE);
- sDataProjectionMap.put(Contacts.CUSTOM_RINGTONE, Contacts.CUSTOM_RINGTONE);
- sDataProjectionMap.put(Contacts.SEND_TO_VOICEMAIL, Contacts.SEND_TO_VOICEMAIL);
- sDataProjectionMap.put(Contacts.LAST_TIME_CONTACTED, Contacts.LAST_TIME_CONTACTED);
- sDataProjectionMap.put(Contacts.TIMES_CONTACTED, Contacts.TIMES_CONTACTED);
- sDataProjectionMap.put(Contacts.STARRED, Contacts.STARRED);
- sDataProjectionMap.put(Contacts.PHOTO_ID, Contacts.PHOTO_ID);
- sDataProjectionMap.put(Contacts.IN_VISIBLE_GROUP, Contacts.IN_VISIBLE_GROUP);
- sDataProjectionMap.put(Contacts.NAME_RAW_CONTACT_ID, Contacts.NAME_RAW_CONTACT_ID);
- sDataProjectionMap.put(GroupMembership.GROUP_SOURCE_ID, GroupMembership.GROUP_SOURCE_ID);
-
- HashMap<String, String> columns;
- columns = new HashMap<String, String>();
- columns.put(RawContacts._ID, RawContacts._ID);
- columns.put(RawContacts.CONTACT_ID, RawContacts.CONTACT_ID);
- columns.put(RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_NAME);
- columns.put(RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_TYPE);
- columns.put(RawContacts.SOURCE_ID, RawContacts.SOURCE_ID);
- columns.put(RawContacts.VERSION, RawContacts.VERSION);
- columns.put(RawContacts.DIRTY, RawContacts.DIRTY);
- columns.put(RawContacts.DELETED, RawContacts.DELETED);
- columns.put(RawContacts.IS_RESTRICTED, RawContacts.IS_RESTRICTED);
- columns.put(RawContacts.SYNC1, RawContacts.SYNC1);
- columns.put(RawContacts.SYNC2, RawContacts.SYNC2);
- columns.put(RawContacts.SYNC3, RawContacts.SYNC3);
- columns.put(RawContacts.SYNC4, RawContacts.SYNC4);
- columns.put(RawContacts.NAME_VERIFIED, RawContacts.NAME_VERIFIED);
- columns.put(Data.RES_PACKAGE, Data.RES_PACKAGE);
- columns.put(Data.MIMETYPE, Data.MIMETYPE);
- columns.put(Data.DATA1, Data.DATA1);
- columns.put(Data.DATA2, Data.DATA2);
- columns.put(Data.DATA3, Data.DATA3);
- columns.put(Data.DATA4, Data.DATA4);
- columns.put(Data.DATA5, Data.DATA5);
- columns.put(Data.DATA6, Data.DATA6);
- columns.put(Data.DATA7, Data.DATA7);
- columns.put(Data.DATA8, Data.DATA8);
- columns.put(Data.DATA9, Data.DATA9);
- columns.put(Data.DATA10, Data.DATA10);
- columns.put(Data.DATA11, Data.DATA11);
- columns.put(Data.DATA12, Data.DATA12);
- columns.put(Data.DATA13, Data.DATA13);
- columns.put(Data.DATA14, Data.DATA14);
- columns.put(Data.DATA15, Data.DATA15);
- columns.put(Data.SYNC1, Data.SYNC1);
- columns.put(Data.SYNC2, Data.SYNC2);
- columns.put(Data.SYNC3, Data.SYNC3);
- columns.put(Data.SYNC4, Data.SYNC4);
- columns.put(RawContacts.Entity.DATA_ID, RawContacts.Entity.DATA_ID);
- columns.put(Data.STARRED, Data.STARRED);
- columns.put(Data.DATA_VERSION, Data.DATA_VERSION);
- columns.put(Data.IS_PRIMARY, Data.IS_PRIMARY);
- columns.put(Data.IS_SUPER_PRIMARY, Data.IS_SUPER_PRIMARY);
- columns.put(GroupMembership.GROUP_SOURCE_ID, GroupMembership.GROUP_SOURCE_ID);
- sRawContactsEntityProjectionMap = columns;
-
- // Handle projections for Contacts-level statuses
- addProjection(sDataProjectionMap, Contacts.CONTACT_PRESENCE,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE);
- addProjection(sContactsProjectionMap, Contacts.CONTACT_CHAT_CAPABILITY,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY);
- addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS);
- addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_TIMESTAMP,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
- addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_RES_PACKAGE,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
- addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_LABEL,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL);
- addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_ICON,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON);
-
- // Handle projections for Data-level statuses
- addProjection(sDataProjectionMap, Data.PRESENCE,
- Tables.PRESENCE + "." + StatusUpdates.PRESENCE);
- addProjection(sDataProjectionMap, Data.CONTACT_CHAT_CAPABILITY,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY);
- addProjection(sDataProjectionMap, Data.STATUS,
- StatusUpdatesColumns.CONCRETE_STATUS);
- addProjection(sDataProjectionMap, Data.STATUS_TIMESTAMP,
- StatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
- addProjection(sDataProjectionMap, Data.STATUS_RES_PACKAGE,
- StatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
- addProjection(sDataProjectionMap, Data.STATUS_LABEL,
- StatusUpdatesColumns.CONCRETE_STATUS_LABEL);
- addProjection(sDataProjectionMap, Data.STATUS_ICON,
- StatusUpdatesColumns.CONCRETE_STATUS_ICON);
-
- // Projection map for data grouped by contact (not raw contact) and some data field(s)
- sDistinctDataProjectionMap = new HashMap<String, String>();
- sDistinctDataProjectionMap.put(Data._ID,
- "MIN(" + Data._ID + ") AS " + Data._ID);
- sDistinctDataProjectionMap.put(Data.DATA_VERSION, Data.DATA_VERSION);
- sDistinctDataProjectionMap.put(Data.IS_PRIMARY, Data.IS_PRIMARY);
- sDistinctDataProjectionMap.put(Data.IS_SUPER_PRIMARY, Data.IS_SUPER_PRIMARY);
- sDistinctDataProjectionMap.put(Data.RES_PACKAGE, Data.RES_PACKAGE);
- sDistinctDataProjectionMap.put(Data.MIMETYPE, Data.MIMETYPE);
- sDistinctDataProjectionMap.put(Data.DATA1, Data.DATA1);
- sDistinctDataProjectionMap.put(Data.DATA2, Data.DATA2);
- sDistinctDataProjectionMap.put(Data.DATA3, Data.DATA3);
- sDistinctDataProjectionMap.put(Data.DATA4, Data.DATA4);
- sDistinctDataProjectionMap.put(Data.DATA5, Data.DATA5);
- sDistinctDataProjectionMap.put(Data.DATA6, Data.DATA6);
- sDistinctDataProjectionMap.put(Data.DATA7, Data.DATA7);
- sDistinctDataProjectionMap.put(Data.DATA8, Data.DATA8);
- sDistinctDataProjectionMap.put(Data.DATA9, Data.DATA9);
- sDistinctDataProjectionMap.put(Data.DATA10, Data.DATA10);
- sDistinctDataProjectionMap.put(Data.DATA11, Data.DATA11);
- sDistinctDataProjectionMap.put(Data.DATA12, Data.DATA12);
- sDistinctDataProjectionMap.put(Data.DATA13, Data.DATA13);
- sDistinctDataProjectionMap.put(Data.DATA14, Data.DATA14);
- sDistinctDataProjectionMap.put(Data.DATA15, Data.DATA15);
- sDistinctDataProjectionMap.put(Data.SYNC1, Data.SYNC1);
- sDistinctDataProjectionMap.put(Data.SYNC2, Data.SYNC2);
- sDistinctDataProjectionMap.put(Data.SYNC3, Data.SYNC3);
- sDistinctDataProjectionMap.put(Data.SYNC4, Data.SYNC4);
- sDistinctDataProjectionMap.put(RawContacts.CONTACT_ID, RawContacts.CONTACT_ID);
- sDistinctDataProjectionMap.put(Contacts.LOOKUP_KEY, Contacts.LOOKUP_KEY);
- sDistinctDataProjectionMap.put(Contacts.DISPLAY_NAME, Contacts.DISPLAY_NAME);
- sDistinctDataProjectionMap.put(Contacts.DISPLAY_NAME_ALTERNATIVE,
- Contacts.DISPLAY_NAME_ALTERNATIVE);
- sDistinctDataProjectionMap.put(Contacts.DISPLAY_NAME_SOURCE, Contacts.DISPLAY_NAME_SOURCE);
- sDistinctDataProjectionMap.put(Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME);
- sDistinctDataProjectionMap.put(Contacts.PHONETIC_NAME_STYLE, Contacts.PHONETIC_NAME_STYLE);
- sDistinctDataProjectionMap.put(Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_PRIMARY);
- sDistinctDataProjectionMap.put(Contacts.SORT_KEY_ALTERNATIVE,
- Contacts.SORT_KEY_ALTERNATIVE);
- sDistinctDataProjectionMap.put(Contacts.CUSTOM_RINGTONE, Contacts.CUSTOM_RINGTONE);
- sDistinctDataProjectionMap.put(Contacts.SEND_TO_VOICEMAIL, Contacts.SEND_TO_VOICEMAIL);
- sDistinctDataProjectionMap.put(Contacts.LAST_TIME_CONTACTED, Contacts.LAST_TIME_CONTACTED);
- sDistinctDataProjectionMap.put(Contacts.TIMES_CONTACTED, Contacts.TIMES_CONTACTED);
- sDistinctDataProjectionMap.put(Contacts.STARRED, Contacts.STARRED);
- sDistinctDataProjectionMap.put(Contacts.PHOTO_ID, Contacts.PHOTO_ID);
- sDistinctDataProjectionMap.put(Contacts.IN_VISIBLE_GROUP, Contacts.IN_VISIBLE_GROUP);
- sDistinctDataProjectionMap.put(GroupMembership.GROUP_SOURCE_ID,
- GroupMembership.GROUP_SOURCE_ID);
-
- // Handle projections for Contacts-level statuses
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_PRESENCE,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE);
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_CHAT_CAPABILITY,
- Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY);
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS);
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_TIMESTAMP,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_RES_PACKAGE,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_LABEL,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL);
- addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_ICON,
- ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON);
-
- // Handle projections for Data-level statuses
- addProjection(sDistinctDataProjectionMap, Data.PRESENCE,
- Tables.PRESENCE + "." + StatusUpdates.PRESENCE);
- addProjection(sDistinctDataProjectionMap, Data.CHAT_CAPABILITY,
- Tables.PRESENCE + "." + StatusUpdates.CHAT_CAPABILITY);
- addProjection(sDistinctDataProjectionMap, Data.STATUS,
- StatusUpdatesColumns.CONCRETE_STATUS);
- addProjection(sDistinctDataProjectionMap, Data.STATUS_TIMESTAMP,
- StatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
- addProjection(sDistinctDataProjectionMap, Data.STATUS_RES_PACKAGE,
- StatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
- addProjection(sDistinctDataProjectionMap, Data.STATUS_LABEL,
- StatusUpdatesColumns.CONCRETE_STATUS_LABEL);
- addProjection(sDistinctDataProjectionMap, Data.STATUS_ICON,
- StatusUpdatesColumns.CONCRETE_STATUS_ICON);
-
- sPhoneLookupProjectionMap = new HashMap<String, String>();
- sPhoneLookupProjectionMap.put(PhoneLookup._ID,
- "contacts_view." + Contacts._ID
- + " AS " + PhoneLookup._ID);
- sPhoneLookupProjectionMap.put(PhoneLookup.LOOKUP_KEY,
- "contacts_view." + Contacts.LOOKUP_KEY
- + " AS " + PhoneLookup.LOOKUP_KEY);
- sPhoneLookupProjectionMap.put(PhoneLookup.DISPLAY_NAME,
- "contacts_view." + Contacts.DISPLAY_NAME
- + " AS " + PhoneLookup.DISPLAY_NAME);
- sPhoneLookupProjectionMap.put(PhoneLookup.LAST_TIME_CONTACTED,
- "contacts_view." + Contacts.LAST_TIME_CONTACTED
- + " AS " + PhoneLookup.LAST_TIME_CONTACTED);
- sPhoneLookupProjectionMap.put(PhoneLookup.TIMES_CONTACTED,
- "contacts_view." + Contacts.TIMES_CONTACTED
- + " AS " + PhoneLookup.TIMES_CONTACTED);
- sPhoneLookupProjectionMap.put(PhoneLookup.STARRED,
- "contacts_view." + Contacts.STARRED
- + " AS " + PhoneLookup.STARRED);
- sPhoneLookupProjectionMap.put(PhoneLookup.IN_VISIBLE_GROUP,
- "contacts_view." + Contacts.IN_VISIBLE_GROUP
- + " AS " + PhoneLookup.IN_VISIBLE_GROUP);
- sPhoneLookupProjectionMap.put(PhoneLookup.PHOTO_ID,
- "contacts_view." + Contacts.PHOTO_ID
- + " AS " + PhoneLookup.PHOTO_ID);
- sPhoneLookupProjectionMap.put(PhoneLookup.CUSTOM_RINGTONE,
- "contacts_view." + Contacts.CUSTOM_RINGTONE
- + " AS " + PhoneLookup.CUSTOM_RINGTONE);
- sPhoneLookupProjectionMap.put(PhoneLookup.HAS_PHONE_NUMBER,
- "contacts_view." + Contacts.HAS_PHONE_NUMBER
- + " AS " + PhoneLookup.HAS_PHONE_NUMBER);
- sPhoneLookupProjectionMap.put(PhoneLookup.SEND_TO_VOICEMAIL,
- "contacts_view." + Contacts.SEND_TO_VOICEMAIL
- + " AS " + PhoneLookup.SEND_TO_VOICEMAIL);
- sPhoneLookupProjectionMap.put(PhoneLookup.NUMBER,
- Phone.NUMBER + " AS " + PhoneLookup.NUMBER);
- sPhoneLookupProjectionMap.put(PhoneLookup.TYPE,
- Phone.TYPE + " AS " + PhoneLookup.TYPE);
- sPhoneLookupProjectionMap.put(PhoneLookup.LABEL,
- Phone.LABEL + " AS " + PhoneLookup.LABEL);
-
- // Groups projection map
- columns = new HashMap<String, String>();
- columns.put(Groups._ID, Groups._ID);
- columns.put(Groups.ACCOUNT_NAME, Groups.ACCOUNT_NAME);
- columns.put(Groups.ACCOUNT_TYPE, Groups.ACCOUNT_TYPE);
- columns.put(Groups.SOURCE_ID, Groups.SOURCE_ID);
- columns.put(Groups.DIRTY, Groups.DIRTY);
- columns.put(Groups.VERSION, Groups.VERSION);
- columns.put(Groups.RES_PACKAGE, Groups.RES_PACKAGE);
- columns.put(Groups.TITLE, Groups.TITLE);
- columns.put(Groups.TITLE_RES, Groups.TITLE_RES);
- columns.put(Groups.GROUP_VISIBLE, Groups.GROUP_VISIBLE);
- columns.put(Groups.SYSTEM_ID, Groups.SYSTEM_ID);
- columns.put(Groups.DELETED, Groups.DELETED);
- columns.put(Groups.NOTES, Groups.NOTES);
- columns.put(Groups.SHOULD_SYNC, Groups.SHOULD_SYNC);
- columns.put(Groups.SYNC1, Groups.SYNC1);
- columns.put(Groups.SYNC2, Groups.SYNC2);
- columns.put(Groups.SYNC3, Groups.SYNC3);
- columns.put(Groups.SYNC4, Groups.SYNC4);
- sGroupsProjectionMap = columns;
-
- // RawContacts and groups projection map
- columns = new HashMap<String, String>();
- columns.putAll(sGroupsProjectionMap);
- columns.put(Groups.SUMMARY_COUNT, "(SELECT COUNT(DISTINCT " + ContactsColumns.CONCRETE_ID
- + ") FROM " + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS + " WHERE "
- + Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP + " AND " + Clauses.BELONGS_TO_GROUP
- + ") AS " + Groups.SUMMARY_COUNT);
- columns.put(Groups.SUMMARY_WITH_PHONES, "(SELECT COUNT(DISTINCT "
- + ContactsColumns.CONCRETE_ID + ") FROM "
- + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS + " WHERE "
- + Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP + " AND " + Clauses.BELONGS_TO_GROUP
- + " AND " + Contacts.HAS_PHONE_NUMBER + ") AS " + Groups.SUMMARY_WITH_PHONES);
- sGroupsSummaryProjectionMap = columns;
-
- // Aggregate exception projection map
- columns = new HashMap<String, String>();
- columns.put(AggregationExceptionColumns._ID, Tables.AGGREGATION_EXCEPTIONS + "._id AS _id");
- columns.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE);
- columns.put(AggregationExceptions.RAW_CONTACT_ID1, AggregationExceptions.RAW_CONTACT_ID1);
- columns.put(AggregationExceptions.RAW_CONTACT_ID2, AggregationExceptions.RAW_CONTACT_ID2);
- sAggregationExceptionsProjectionMap = columns;
-
- // Settings projection map
- columns = new HashMap<String, String>();
- columns.put(Settings.ACCOUNT_NAME, Settings.ACCOUNT_NAME);
- columns.put(Settings.ACCOUNT_TYPE, Settings.ACCOUNT_TYPE);
- columns.put(Settings.UNGROUPED_VISIBLE, Settings.UNGROUPED_VISIBLE);
- columns.put(Settings.SHOULD_SYNC, Settings.SHOULD_SYNC);
- columns.put(Settings.ANY_UNSYNCED, "(CASE WHEN MIN(" + Settings.SHOULD_SYNC
- + ",(SELECT (CASE WHEN MIN(" + Groups.SHOULD_SYNC + ") IS NULL THEN 1 ELSE MIN("
- + Groups.SHOULD_SYNC + ") END) FROM " + Tables.GROUPS + " WHERE "
- + GroupsColumns.CONCRETE_ACCOUNT_NAME + "=" + SettingsColumns.CONCRETE_ACCOUNT_NAME
- + " AND " + GroupsColumns.CONCRETE_ACCOUNT_TYPE + "="
- + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "))=0 THEN 1 ELSE 0 END) AS "
- + Settings.ANY_UNSYNCED);
- columns.put(Settings.UNGROUPED_COUNT, "(SELECT COUNT(*) FROM (SELECT 1 FROM "
- + Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS + " GROUP BY "
- + Clauses.GROUP_BY_ACCOUNT_CONTACT_ID + " HAVING " + Clauses.HAVING_NO_GROUPS
- + ")) AS " + Settings.UNGROUPED_COUNT);
- columns.put(Settings.UNGROUPED_WITH_PHONES, "(SELECT COUNT(*) FROM (SELECT 1 FROM "
- + Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS + " WHERE "
- + Contacts.HAS_PHONE_NUMBER + " GROUP BY " + Clauses.GROUP_BY_ACCOUNT_CONTACT_ID
- + " HAVING " + Clauses.HAVING_NO_GROUPS + ")) AS "
- + Settings.UNGROUPED_WITH_PHONES);
- sSettingsProjectionMap = columns;
-
- columns = new HashMap<String, String>();
- columns.put(PresenceColumns.RAW_CONTACT_ID, PresenceColumns.RAW_CONTACT_ID);
- columns.put(StatusUpdates.DATA_ID,
- DataColumns.CONCRETE_ID + " AS " + StatusUpdates.DATA_ID);
- columns.put(StatusUpdates.IM_ACCOUNT, StatusUpdates.IM_ACCOUNT);
- columns.put(StatusUpdates.IM_HANDLE, StatusUpdates.IM_HANDLE);
- columns.put(StatusUpdates.PROTOCOL, StatusUpdates.PROTOCOL);
- // We cannot allow a null in the custom protocol field, because SQLite3 does not
- // properly enforce uniqueness of null values
- columns.put(StatusUpdates.CUSTOM_PROTOCOL, "(CASE WHEN " + StatusUpdates.CUSTOM_PROTOCOL
- + "='' THEN NULL ELSE " + StatusUpdates.CUSTOM_PROTOCOL + " END) AS "
- + StatusUpdates.CUSTOM_PROTOCOL);
- columns.put(StatusUpdates.PRESENCE, StatusUpdates.PRESENCE);
- columns.put(StatusUpdates.CHAT_CAPABILITY, StatusUpdates.CHAT_CAPABILITY);
- columns.put(StatusUpdates.STATUS, StatusUpdates.STATUS);
- columns.put(StatusUpdates.STATUS_TIMESTAMP, StatusUpdates.STATUS_TIMESTAMP);
- columns.put(StatusUpdates.STATUS_RES_PACKAGE, StatusUpdates.STATUS_RES_PACKAGE);
- columns.put(StatusUpdates.STATUS_ICON, StatusUpdates.STATUS_ICON);
- columns.put(StatusUpdates.STATUS_LABEL, StatusUpdates.STATUS_LABEL);
- sStatusUpdatesProjectionMap = columns;
-
- // Live folder projection
- sLiveFoldersProjectionMap = new HashMap<String, String>();
- sLiveFoldersProjectionMap.put(LiveFolders._ID,
- Contacts._ID + " AS " + LiveFolders._ID);
- sLiveFoldersProjectionMap.put(LiveFolders.NAME,
- Contacts.DISPLAY_NAME + " AS " + LiveFolders.NAME);
- // TODO: Put contact photo back when we have a way to display a default icon
- // for contacts without a photo
- // sLiveFoldersProjectionMap.put(LiveFolders.ICON_BITMAP,
- // Photos.DATA + " AS " + LiveFolders.ICON_BITMAP);
+ private static class DirectoryInfo {
+ String authority;
+ String accountName;
+ String accountType;
}
- private static void addProjection(HashMap<String, String> map, String toField, String fromField) {
- map.put(toField, fromField + " AS " + toField);
- }
+ /**
+ * Cached information about contact directories.
+ */
+ private HashMap<String, DirectoryInfo> mDirectoryCache = new HashMap<String, DirectoryInfo>();
+ private boolean mDirectoryCacheValid = false;
/**
* Handles inserts and update for a specific Data type.
@@ -1237,7 +1229,7 @@
* is not provided, generate one by concatenating first name and last
* name.
*/
- private void fixStructuredNameComponents(ContentValues augmented, ContentValues update) {
+ public void fixStructuredNameComponents(ContentValues augmented, ContentValues update) {
final String unstruct = update.getAsString(StructuredName.DISPLAY_NAME);
final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct);
@@ -1604,14 +1596,18 @@
long dataId;
if (values.containsKey(Phone.NUMBER)) {
String number = values.getAsString(Phone.NUMBER);
- String normalizedNumber = computeNormalizedNumber(number);
- values.put(PhoneColumns.NORMALIZED_NUMBER, normalizedNumber);
+
+ String numberE164 =
+ PhoneNumberUtils.formatNumberToE164(number, getCurrentCountryIso());
+ if (numberE164 != null) {
+ values.put(PhoneColumns.NORMALIZED_NUMBER, numberE164);
+ }
dataId = super.insert(db, rawContactId, values);
- updatePhoneLookup(db, rawContactId, dataId, number, normalizedNumber);
+ updatePhoneLookup(db, rawContactId, dataId, number, numberE164);
mContactAggregator.updateHasPhoneNumber(db, rawContactId);
fixRawContactDisplayName(db, rawContactId);
- if (normalizedNumber != null) {
+ if (numberE164 != null) {
triggerAggregation(rawContactId);
}
} else {
@@ -1625,10 +1621,16 @@
boolean callerIsSyncAdapter) {
String number = null;
String normalizedNumber = null;
+ String numberE164 = null;
if (values.containsKey(Phone.NUMBER)) {
number = values.getAsString(Phone.NUMBER);
- normalizedNumber = computeNormalizedNumber(number);
- values.put(PhoneColumns.NORMALIZED_NUMBER, normalizedNumber);
+ if (number != null) {
+ numberE164 =
+ PhoneNumberUtils.formatNumberToE164(number, getCurrentCountryIso());
+ }
+ if (numberE164 != null) {
+ values.put(PhoneColumns.NORMALIZED_NUMBER, numberE164);
+ }
}
if (!super.update(db, values, c, callerIsSyncAdapter)) {
@@ -1638,7 +1640,7 @@
if (values.containsKey(Phone.NUMBER)) {
long dataId = c.getLong(DataUpdateQuery._ID);
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
- updatePhoneLookup(db, rawContactId, dataId, number, normalizedNumber);
+ updatePhoneLookup(db, rawContactId, dataId, number, numberE164);
mContactAggregator.updateHasPhoneNumber(db, rawContactId);
fixRawContactDisplayName(db, rawContactId);
triggerAggregation(rawContactId);
@@ -1660,28 +1662,28 @@
return count;
}
- private String computeNormalizedNumber(String number) {
- String normalizedNumber = null;
- if (number != null) {
- normalizedNumber = PhoneNumberUtils.getStrippedReversed(number);
- }
- return normalizedNumber;
- }
-
private void updatePhoneLookup(SQLiteDatabase db, long rawContactId, long dataId,
- String number, String normalizedNumber) {
+ String number, String numberE164) {
+ mSelectionArgs1[0] = String.valueOf(dataId);
+ db.delete(Tables.PHONE_LOOKUP, PhoneLookupColumns.DATA_ID + "=?", mSelectionArgs1);
if (number != null) {
- ContentValues phoneValues = new ContentValues();
- phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactId);
- phoneValues.put(PhoneLookupColumns.DATA_ID, dataId);
- phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber);
- phoneValues.put(PhoneLookupColumns.MIN_MATCH,
- PhoneNumberUtils.toCallerIDMinMatch(number));
+ String normalizedNumber = PhoneNumberUtils.normalizeNumber(number);
+ if (!TextUtils.isEmpty(normalizedNumber)) {
+ ContentValues phoneValues = new ContentValues();
+ phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactId);
+ phoneValues.put(PhoneLookupColumns.DATA_ID, dataId);
+ phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber);
+ phoneValues.put(PhoneLookupColumns.MIN_MATCH,
+ PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber));
+ db.insert(Tables.PHONE_LOOKUP, null, phoneValues);
- db.replace(Tables.PHONE_LOOKUP, null, phoneValues);
- } else {
- mSelectionArgs1[0] = String.valueOf(dataId);
- db.delete(Tables.PHONE_LOOKUP, PhoneLookupColumns.DATA_ID + "=?", mSelectionArgs1);
+ if (numberE164 != null && !numberE164.equals(normalizedNumber)) {
+ phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, numberE164);
+ phoneValues.put(PhoneLookupColumns.MIN_MATCH,
+ PhoneNumberUtils.toCallerIDMinMatch(numberE164));
+ db.insert(Tables.PHONE_LOOKUP, null, phoneValues);
+ }
+ }
}
}
@@ -1703,6 +1705,16 @@
public class GroupMembershipRowHandler extends DataRowHandler {
+ private static final String SELECTION_RAW_CONTACT_ID = RawContacts._ID + "=?";
+
+ private static final String QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID =
+ "SELECT COUNT(*) FROM " + Tables.DATA + " LEFT OUTER JOIN " + Tables .GROUPS
+ + " ON " + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID
+ + "=" + GroupsColumns.CONCRETE_ID
+ + " WHERE " + DataColumns.MIMETYPE_ID + "=?"
+ + " AND " + Tables.DATA + "." + GroupMembership.RAW_CONTACT_ID + "=?"
+ + " AND " + Groups.FAVORITES + "!=0";
+
public GroupMembershipRowHandler() {
super(GroupMembership.CONTENT_ITEM_TYPE);
}
@@ -1711,6 +1723,9 @@
public long insert(SQLiteDatabase db, long rawContactId, ContentValues values) {
resolveGroupSourceIdInValues(rawContactId, db, values, true);
long dataId = super.insert(db, rawContactId, values);
+ if (hasFavoritesGroupMembership(db, rawContactId)) {
+ updateRawContactsStar(db, rawContactId, true /* starred */);
+ }
updateVisibility(rawContactId);
return dataId;
}
@@ -1719,18 +1734,46 @@
public boolean update(SQLiteDatabase db, ContentValues values, Cursor c,
boolean callerIsSyncAdapter) {
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
+ boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId);
resolveGroupSourceIdInValues(rawContactId, db, values, false);
if (!super.update(db, values, c, callerIsSyncAdapter)) {
return false;
}
+ boolean isStarred = hasFavoritesGroupMembership(db, rawContactId);
+ if (wasStarred != isStarred) {
+ updateRawContactsStar(db, rawContactId, isStarred);
+ }
updateVisibility(rawContactId);
return true;
}
+ private void updateRawContactsStar(SQLiteDatabase db, long rawContactId, boolean starred) {
+ ContentValues rawContactValues = new ContentValues();
+ rawContactValues.put(RawContacts.STARRED, starred ? 1 : 0);
+ if (db.update(Tables.RAW_CONTACTS, rawContactValues, SELECTION_RAW_CONTACT_ID,
+ new String[]{Long.toString(rawContactId)}) > 0) {
+ mContactAggregator.updateStarred(rawContactId);
+ }
+ }
+
+ private boolean hasFavoritesGroupMembership(SQLiteDatabase db, long rawContactId) {
+ final long groupMembershipMimetypeId = mDbHelper
+ .getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
+ boolean isStarred = 0 < DatabaseUtils
+ .longForQuery(db, QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID,
+ new String[]{Long.toString(groupMembershipMimetypeId), Long.toString(rawContactId)});
+ return isStarred;
+ }
+
@Override
public int delete(SQLiteDatabase db, Cursor c) {
long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+ boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId);
int count = super.delete(db, c);
+ boolean isStarred = hasFavoritesGroupMembership(db, rawContactId);
+ if (wasStarred && !isStarred) {
+ updateRawContactsStar(db, rawContactId, false /* starred */);
+ }
updateVisibility(rawContactId);
return count;
}
@@ -1832,6 +1875,7 @@
// is a list of groups with this group id.
private HashMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache = Maps.newHashMap();
+ private ContactDirectoryManager mContactDirectoryManager;
private ContactAggregator mContactAggregator;
private LegacyApiSupport mLegacyApiSupport;
private GlobalSearchSupport mGlobalSearchSupport;
@@ -1857,6 +1901,8 @@
private Locale mCurrentLocale;
+ private CountryMonitor mCountryMonitor;
+
@Override
public boolean onCreate() {
@@ -1871,13 +1917,11 @@
private boolean initialize() {
final Context context = getContext();
+ mCountryMonitor = CountryMonitor.getInstance(context);
mDbHelper = (ContactsDatabaseHelper)getDatabaseHelper();
+ mContactDirectoryManager = new ContactDirectoryManager(this);
mGlobalSearchSupport = new GlobalSearchSupport(this);
mLegacyApiSupport = new LegacyApiSupport(context, mDbHelper, this, mGlobalSearchSupport);
- mContactAggregator = new ContactAggregator(this, mDbHelper,
- createPhotoPriorityResolver(context));
- mContactAggregator.setEnabled(SystemProperties.getBoolean(AGGREGATE_CONTACTS, true));
-
mDb = mDbHelper.getWritableDatabase();
initForDefaultLocale();
@@ -1999,6 +2043,8 @@
verifyLocale();
}
+ startContactDirectoryManager();
+
if (isAggregationUpgradeNeeded()) {
upgradeAggregationAlgorithm();
}
@@ -2006,6 +2052,10 @@
return (mDb != null);
}
+ protected String getCurrentCountryIso() {
+ return mCountryMonitor.getCountryIso();
+ }
+
private void initDataRowHandlers() {
mDataRowHandlers = new HashMap<String, DataRowHandler>();
@@ -2024,6 +2074,7 @@
mDataRowHandlers.put(GroupMembership.CONTENT_ITEM_TYPE, new GroupMembershipRowHandler());
mDataRowHandlers.put(Photo.CONTENT_ITEM_TYPE, new PhotoDataRowHandler());
}
+
/**
* Visible for testing.
*/
@@ -2041,11 +2092,14 @@
mPostalSplitter = new PostalSplitter(mCurrentLocale);
mCommonNicknameCache = new CommonNicknameCache(mDbHelper.getReadableDatabase());
ContactLocaleUtils.getIntance().setLocale(mCurrentLocale);
+ mContactAggregator = new ContactAggregator(this, mDbHelper,
+ createPhotoPriorityResolver(getContext()), mNameSplitter, mCommonNicknameCache);
+ mContactAggregator.setEnabled(SystemProperties.getBoolean(AGGREGATE_CONTACTS, true));
+
initDataRowHandlers();
}
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onLocaleChanged() {
if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
return;
}
@@ -2056,7 +2110,7 @@
protected void verifyAccounts() {
AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
- onAccountsUpdated(AccountManager.get(getContext()).getAccounts());
+ updateAccounts(AccountManager.get(getContext()).getAccounts());
}
/**
@@ -2119,10 +2173,20 @@
}
/* Visible for testing */
+ public ContactDirectoryManager getContactDirectoryManager() {
+ return mContactDirectoryManager;
+ }
+
+ /* Visible for testing */
protected Locale getLocale() {
return Locale.getDefault();
}
+ /* Visible for testing */
+ protected void startContactDirectoryManager() {
+ getContactDirectoryManager().start();
+ }
+
protected boolean isLegacyContactImportNeeded() {
int version = Integer.parseInt(mDbHelper.getProperty(PROPERTY_CONTACTS_IMPORTED, "0"));
return version < PROPERTY_CONTACTS_IMPORT_VERSION;
@@ -2425,7 +2489,7 @@
}
case RAW_CONTACTS: {
- id = insertRawContact(uri, values);
+ id = insertRawContact(uri, values, callerIsSyncAdapter);
mSyncToNetwork |= !callerIsSyncAdapter;
break;
}
@@ -2552,9 +2616,10 @@
*
* @param uri the values for the new row
* @param values the account this contact should be associated with. may be null.
+ * @param callerIsSyncAdapter
* @return the row ID of the newly created row
*/
- private long insertRawContact(Uri uri, ContentValues values) {
+ private long insertRawContact(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
mValues.clear();
mValues.putAll(values);
mValues.putNull(RawContacts.CONTACT_ID);
@@ -2576,9 +2641,69 @@
// Trigger creation of a Contact based on this RawContact at the end of transaction
mInsertedRawContacts.put(rawContactId, account);
+ if (!callerIsSyncAdapter) {
+ addAutoAddMembership(rawContactId);
+ final Long starred = values.getAsLong(RawContacts.STARRED);
+ if (starred != null && starred != 0) {
+ updateFavoritesMembership(rawContactId, starred != 0);
+ }
+ }
+
return rawContactId;
}
+ private void addAutoAddMembership(long rawContactId) {
+ final Long groupId = findGroupByRawContactId(SELECTION_AUTO_ADD_GROUPS_BY_RAW_CONTACT_ID,
+ rawContactId);
+ if (groupId != null) {
+ insertDataGroupMembership(rawContactId, groupId);
+ }
+ }
+
+ private Long findGroupByRawContactId(String selection, long rawContactId) {
+ Cursor c = mDb.query(Tables.GROUPS + "," + Tables.RAW_CONTACTS, PROJECTION_GROUP_ID,
+ selection,
+ new String[]{Long.toString(rawContactId)},
+ null /* groupBy */, null /* having */, null /* orderBy */);
+ try {
+ while (c.moveToNext()) {
+ return c.getLong(0);
+ }
+ return null;
+ } finally {
+ c.close();
+ }
+ }
+
+ private void updateFavoritesMembership(long rawContactId, boolean isStarred) {
+ final Long groupId = findGroupByRawContactId(SELECTION_FAVORITES_GROUPS_BY_RAW_CONTACT_ID,
+ rawContactId);
+ if (groupId != null) {
+ if (isStarred) {
+ insertDataGroupMembership(rawContactId, groupId);
+ } else {
+ deleteDataGroupMembership(rawContactId, groupId);
+ }
+ }
+ }
+
+ private void insertDataGroupMembership(long rawContactId, long groupId) {
+ ContentValues groupMembershipValues = new ContentValues();
+ groupMembershipValues.put(GroupMembership.GROUP_ROW_ID, groupId);
+ groupMembershipValues.put(GroupMembership.RAW_CONTACT_ID, rawContactId);
+ groupMembershipValues.put(DataColumns.MIMETYPE_ID,
+ mDbHelper.getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE));
+ mDb.insert(Tables.DATA, null, groupMembershipValues);
+ }
+
+ private void deleteDataGroupMembership(long rawContactId, long groupId) {
+ final String[] selectionArgs = {
+ Long.toString(mDbHelper.getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE)),
+ Long.toString(groupId),
+ Long.toString(rawContactId)};
+ mDb.delete(Tables.DATA, SELECTION_GROUPMEMBERSHIP_DATA, selectionArgs);
+ }
+
/**
* Inserts an item in the data table
*
@@ -3014,12 +3139,45 @@
}
mValues.remove(Groups.RES_PACKAGE);
+ final boolean isFavoritesGroup = mValues.getAsLong(Groups.FAVORITES) != null
+ ? mValues.getAsLong(Groups.FAVORITES) != 0
+ : false;
+
if (!callerIsSyncAdapter) {
mValues.put(Groups.DIRTY, 1);
}
long result = mDb.insert(Tables.GROUPS, Groups.TITLE, mValues);
+ if (!callerIsSyncAdapter && isFavoritesGroup) {
+ // add all starred raw contacts to this group
+ String selection;
+ String[] selectionArgs;
+ if (account == null) {
+ selection = RawContacts.ACCOUNT_NAME + " IS NULL AND "
+ + RawContacts.ACCOUNT_TYPE + " IS NULL";
+ selectionArgs = null;
+ } else {
+ selection = RawContacts.ACCOUNT_NAME + "=? AND "
+ + RawContacts.ACCOUNT_TYPE + "=?";
+ selectionArgs = new String[]{account.name, account.type};
+ }
+ Cursor c = mDb.query(Tables.RAW_CONTACTS,
+ new String[]{RawContacts._ID, RawContacts.STARRED},
+ selection, selectionArgs, null, null, null);
+ try {
+ while (c.moveToNext()) {
+ if (c.getLong(1) != 0) {
+ final long rawContactId = c.getLong(0);
+ insertDataGroupMembership(rawContactId, result);
+ setRawContactDirty(rawContactId);
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
if (mValues.containsKey(Groups.GROUP_VISIBLE)) {
mVisibleTouched = true;
}
@@ -3121,7 +3279,7 @@
try {
cursor = mDb.query(DataContactsQuery.TABLE, DataContactsQuery.PROJECTION,
mSb.toString(), mSelectionArgs.toArray(EMPTY_STRING_ARRAY), null, null,
- Contacts.IN_VISIBLE_GROUP + " DESC, " + Data.RAW_CONTACT_ID);
+ Clauses.CONTACT_VISIBLE + " DESC, " + Data.RAW_CONTACT_ID);
if (cursor.moveToFirst()) {
dataId = cursor.getLong(DataContactsQuery.DATA_ID);
rawContactId = cursor.getLong(DataContactsQuery.RAW_CONTACT_ID);
@@ -3251,7 +3409,7 @@
case CONTACTS_ID: {
long contactId = ContentUris.parseId(uri);
- return deleteContact(contactId);
+ return deleteContact(contactId, callerIsSyncAdapter);
}
case CONTACTS_LOOKUP: {
@@ -3263,16 +3421,16 @@
}
final String lookupKey = pathSegments.get(2);
final long contactId = lookupContactIdByLookupKey(mDb, lookupKey);
- return deleteContact(contactId);
+ return deleteContact(contactId, callerIsSyncAdapter);
}
case CONTACTS_LOOKUP_ID: {
// lookup contact by id and lookup key to see if they still match the actual record
- long contactId = ContentUris.parseId(uri);
final List<String> pathSegments = uri.getPathSegments();
final String lookupKey = pathSegments.get(2);
SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
setTablesAndProjectionMapForContacts(lookupQb, uri, null);
+ long contactId = ContentUris.parseId(uri);
String[] args;
if (selectionArgs == null) {
args = new String[2];
@@ -3288,7 +3446,7 @@
try {
if (c.getCount() == 1) {
// contact was unmodified so go ahead and delete it
- return deleteContact(contactId);
+ return deleteContact(contactId, callerIsSyncAdapter);
} else {
// row was changed (e.g. the merging might have changed), we got multiple
// rows or the supplied selection filtered the record out
@@ -3405,7 +3563,7 @@
return count;
}
- private int deleteContact(long contactId) {
+ private int deleteContact(long contactId, boolean callerIsSyncAdapter) {
mSelectionArgs1[0] = Long.toString(contactId);
Cursor c = mDb.query(Tables.RAW_CONTACTS, new String[]{RawContacts._ID},
RawContacts.CONTACT_ID + "=?", mSelectionArgs1,
@@ -3413,7 +3571,7 @@
try {
while (c.moveToNext()) {
long rawContactId = c.getLong(0);
- markRawContactAsDeleted(rawContactId);
+ markRawContactAsDeleted(rawContactId, callerIsSyncAdapter);
}
} finally {
c.close();
@@ -3431,7 +3589,7 @@
return count;
} else {
mDbHelper.removeContactIfSingleton(rawContactId);
- return markRawContactAsDeleted(rawContactId);
+ return markRawContactAsDeleted(rawContactId, callerIsSyncAdapter);
}
}
@@ -3446,7 +3604,7 @@
return mDb.delete(Tables.PRESENCE, selection, selectionArgs);
}
- private int markRawContactAsDeleted(long rawContactId) {
+ private int markRawContactAsDeleted(long rawContactId, boolean callerIsSyncAdapter) {
mSyncToNetwork = true;
mValues.clear();
@@ -3455,7 +3613,7 @@
mValues.put(RawContactsColumns.AGGREGATION_NEEDED, 1);
mValues.putNull(RawContacts.CONTACT_ID);
mValues.put(RawContacts.DIRTY, 1);
- return updateRawContact(rawContactId, mValues);
+ return updateRawContact(rawContactId, mValues, callerIsSyncAdapter);
}
@Override
@@ -3492,12 +3650,12 @@
}
case CONTACTS: {
- count = updateContactOptions(values, selection, selectionArgs);
+ count = updateContactOptions(values, selection, selectionArgs, callerIsSyncAdapter);
break;
}
case CONTACTS_ID: {
- count = updateContactOptions(ContentUris.parseId(uri), values);
+ count = updateContactOptions(ContentUris.parseId(uri), values, callerIsSyncAdapter);
break;
}
@@ -3511,7 +3669,7 @@
}
final String lookupKey = pathSegments.get(2);
final long contactId = lookupContactIdByLookupKey(mDb, lookupKey);
- count = updateContactOptions(contactId, values);
+ count = updateContactOptions(contactId, values, callerIsSyncAdapter);
break;
}
@@ -3547,7 +3705,7 @@
case RAW_CONTACTS: {
selection = appendAccountToSelection(uri, selection);
- count = updateRawContacts(values, selection, selectionArgs);
+ count = updateRawContacts(values, selection, selectionArgs, callerIsSyncAdapter);
break;
}
@@ -3556,10 +3714,12 @@
if (selection != null) {
selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(rawContactId));
count = updateRawContacts(values, RawContacts._ID + "=?"
- + " AND(" + selection + ")", selectionArgs);
+ + " AND(" + selection + ")", selectionArgs,
+ callerIsSyncAdapter);
} else {
mSelectionArgs1[0] = String.valueOf(rawContactId);
- count = updateRawContacts(values, RawContacts._ID + "=?", mSelectionArgs1);
+ count = updateRawContacts(values, RawContacts._ID + "=?", mSelectionArgs1,
+ callerIsSyncAdapter);
}
break;
}
@@ -3603,6 +3763,12 @@
break;
}
+ case DIRECTORIES: {
+ mContactDirectoryManager.scheduleDirectoryUpdateForCaller();
+ count = 1;
+ break;
+ }
+
default: {
mSyncToNetwork = true;
return mLegacyApiSupport.update(uri, values, selection, selectionArgs);
@@ -3724,12 +3890,18 @@
return count;
}
- private int updateRawContacts(ContentValues values, String selection, String[] selectionArgs) {
+ private int updateRawContacts(ContentValues values, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter) {
if (values.containsKey(RawContacts.CONTACT_ID)) {
throw new IllegalArgumentException(RawContacts.CONTACT_ID + " should not be included " +
"in content values. Contact IDs are assigned automatically");
}
+ if (!callerIsSyncAdapter) {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ RawContacts.RAW_CONTACT_IS_READ_ONLY + "=0");
+ }
+
int count = 0;
Cursor cursor = mDb.query(mDbHelper.getRawContactView(),
new String[] { RawContacts._ID }, selection,
@@ -3737,7 +3909,7 @@
try {
while (cursor.moveToNext()) {
long rawContactId = cursor.getLong(0);
- updateRawContact(rawContactId, values);
+ updateRawContact(rawContactId, values, callerIsSyncAdapter);
count++;
}
} finally {
@@ -3747,7 +3919,8 @@
return count;
}
- private int updateRawContact(long rawContactId, ContentValues values) {
+ private int updateRawContact(long rawContactId, ContentValues values,
+ boolean callerIsSyncAdapter) {
final String selection = RawContacts._ID + " = ?";
mSelectionArgs1[0] = Long.toString(rawContactId);
final boolean requestUndoDelete = (values.containsKey(RawContacts.DELETED)
@@ -3783,8 +3956,30 @@
}
}
if (values.containsKey(RawContacts.STARRED)) {
+ if (!callerIsSyncAdapter) {
+ updateFavoritesMembership(rawContactId,
+ values.getAsLong(RawContacts.STARRED) != 0);
+ }
mContactAggregator.updateStarred(rawContactId);
+ } else {
+ // if this raw contact is being associated with an account, then update the
+ // favorites group membership based on whether or not this contact is starred.
+ // If it is starred, add a group membership, if one doesn't already exist
+ // otherwise delete any matching group memberships.
+ if (!callerIsSyncAdapter && values.containsKey(RawContacts.ACCOUNT_NAME)) {
+ boolean starred = 0 != DatabaseUtils.longForQuery(mDb,
+ SELECTION_STARRED_FROM_RAW_CONTACTS,
+ new String[]{Long.toString(rawContactId)});
+ updateFavoritesMembership(rawContactId, starred);
+ }
}
+
+ // if this raw contact is being associated with an account, then add a
+ // group membership to the group marked as AutoAdd, if any.
+ if (!callerIsSyncAdapter && values.containsKey(RawContacts.ACCOUNT_NAME)) {
+ addAutoAddMembership(rawContactId);
+ }
+
if (values.containsKey(RawContacts.SOURCE_ID)) {
mContactAggregator.updateLookupKeyForRawContact(mDb, rawContactId);
}
@@ -3835,6 +4030,11 @@
mValues.remove(Data.IS_PRIMARY);
}
+ if (!callerIsSyncAdapter) {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ Data.IS_READ_ONLY + "=0");
+ }
+
int count = 0;
// Note that the query will return data according to the access restrictions,
@@ -3866,7 +4066,7 @@
}
private int updateContactOptions(ContentValues values, String selection,
- String[] selectionArgs) {
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
int count = 0;
Cursor cursor = mDb.query(mDbHelper.getContactView(),
new String[] { Contacts._ID }, selection,
@@ -3874,7 +4074,7 @@
try {
while (cursor.moveToNext()) {
long contactId = cursor.getLong(0);
- updateContactOptions(contactId, values);
+ updateContactOptions(contactId, values, callerIsSyncAdapter);
count++;
}
} finally {
@@ -3884,7 +4084,8 @@
return count;
}
- private int updateContactOptions(long contactId, ContentValues values) {
+ private int updateContactOptions(long contactId, ContentValues values,
+ boolean callerIsSyncAdapter) {
mValues.clear();
ContactsDatabaseHelper.copyStringValue(mValues, RawContacts.CUSTOM_RINGTONE,
@@ -3909,7 +4110,23 @@
}
mSelectionArgs1[0] = String.valueOf(contactId);
- mDb.update(Tables.RAW_CONTACTS, mValues, RawContacts.CONTACT_ID + "=?", mSelectionArgs1);
+ mDb.update(Tables.RAW_CONTACTS, mValues, RawContacts.CONTACT_ID + "=?"
+ + " AND " + RawContacts.RAW_CONTACT_IS_READ_ONLY + "=0", mSelectionArgs1);
+
+ if (mValues.containsKey(RawContacts.STARRED) && !callerIsSyncAdapter) {
+ Cursor cursor = mDb.query(mDbHelper.getRawContactView(),
+ new String[] { RawContacts._ID }, RawContacts.CONTACT_ID + "=?",
+ mSelectionArgs1, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ long rawContactId = cursor.getLong(0);
+ updateFavoritesMembership(rawContactId,
+ mValues.getAsLong(RawContacts.STARRED) != 0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
// Copy changeable values to prevent automatically managed fields from
// being explicitly updated by clients.
@@ -3978,44 +4195,25 @@
return 1;
}
- /**
- * Check whether GOOGLE_MY_CONTACTS_GROUP exists, otherwise create it.
- *
- * @return the group id
- */
- private long getOrCreateMyContactsGroupInTransaction(String accountName, String accountType) {
- Cursor cursor = mDb.query(Tables.GROUPS, new String[] {"_id"},
- Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =? AND "
- + Groups.TITLE + " =?",
- new String[] {accountName, accountType, GOOGLE_MY_CONTACTS_GROUP_TITLE},
- null, null, null);
- try {
- if(cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- } finally {
- cursor.close();
+ public void onAccountsUpdated(Account[] accounts) {
+ boolean accountsChanged = updateAccounts(accounts);
+ if (accountsChanged) {
+ mContactDirectoryManager.scheduleScanAllPackages(true);
}
-
- ContentValues values = new ContentValues();
- values.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP_TITLE);
- values.put(Groups.ACCOUNT_NAME, accountName);
- values.put(Groups.ACCOUNT_TYPE, accountType);
- values.put(Groups.GROUP_VISIBLE, "1");
- return mDb.insert(Tables.GROUPS, null, values);
}
- public void onAccountsUpdated(Account[] accounts) {
+ private boolean updateAccounts(Account[] accounts) {
// TODO : Check the unit test.
+ boolean accountsChanged = false;
HashSet<Account> existingAccounts = new HashSet<Account>();
- boolean hasUnassignedContacts[] = new boolean[]{false};
mDb.beginTransaction();
try {
- findValidAccounts(existingAccounts, hasUnassignedContacts);
+ findValidAccounts(existingAccounts);
// Add a row to the ACCOUNTS table for each new account
for (Account account : accounts) {
if (!existingAccounts.contains(account)) {
+ accountsChanged = true;
mDb.execSQL("INSERT INTO " + Tables.ACCOUNTS + " (" + RawContacts.ACCOUNT_NAME
+ ", " + RawContacts.ACCOUNT_TYPE + ") VALUES (?, ?)",
new String[] {account.name, account.type});
@@ -4029,38 +4227,44 @@
accountsToDelete.remove(account);
}
- for (Account account : accountsToDelete) {
- Log.d(TAG, "removing data for removed account " + account);
- String[] params = new String[] {account.name, account.type};
- mDb.execSQL(
- "DELETE FROM " + Tables.GROUPS +
- " WHERE " + Groups.ACCOUNT_NAME + " = ?" +
- " AND " + Groups.ACCOUNT_TYPE + " = ?", params);
- mDb.execSQL(
- "DELETE FROM " + Tables.PRESENCE +
- " WHERE " + PresenceColumns.RAW_CONTACT_ID + " IN (" +
- "SELECT " + RawContacts._ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.ACCOUNT_NAME + " = ?" +
- " AND " + RawContacts.ACCOUNT_TYPE + " = ?)", params);
- mDb.execSQL(
- "DELETE FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.ACCOUNT_NAME + " = ?" +
- " AND " + RawContacts.ACCOUNT_TYPE + " = ?", params);
- mDb.execSQL(
- "DELETE FROM " + Tables.SETTINGS +
- " WHERE " + Settings.ACCOUNT_NAME + " = ?" +
- " AND " + Settings.ACCOUNT_TYPE + " = ?", params);
- mDb.execSQL(
- "DELETE FROM " + Tables.ACCOUNTS +
- " WHERE " + RawContacts.ACCOUNT_NAME + "=?" +
- " AND " + RawContacts.ACCOUNT_TYPE + "=?", params);
- }
-
if (!accountsToDelete.isEmpty()) {
+ accountsChanged = true;
+ for (Account account : accountsToDelete) {
+ Log.d(TAG, "removing data for removed account " + account);
+ String[] params = new String[] {account.name, account.type};
+ mDb.execSQL(
+ "DELETE FROM " + Tables.GROUPS +
+ " WHERE " + Groups.ACCOUNT_NAME + " = ?" +
+ " AND " + Groups.ACCOUNT_TYPE + " = ?", params);
+ mDb.execSQL(
+ "DELETE FROM " + Tables.PRESENCE +
+ " WHERE " + PresenceColumns.RAW_CONTACT_ID + " IN (" +
+ "SELECT " + RawContacts._ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.ACCOUNT_NAME + " = ?" +
+ " AND " + RawContacts.ACCOUNT_TYPE + " = ?)", params);
+ mDb.execSQL(
+ "DELETE FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.ACCOUNT_NAME + " = ?" +
+ " AND " + RawContacts.ACCOUNT_TYPE + " = ?", params);
+ mDb.execSQL(
+ "DELETE FROM " + Tables.SETTINGS +
+ " WHERE " + Settings.ACCOUNT_NAME + " = ?" +
+ " AND " + Settings.ACCOUNT_TYPE + " = ?", params);
+ mDb.execSQL(
+ "DELETE FROM " + Tables.ACCOUNTS +
+ " WHERE " + RawContacts.ACCOUNT_NAME + "=?" +
+ " AND " + RawContacts.ACCOUNT_TYPE + "=?", params);
+ mDb.execSQL(
+ "DELETE FROM " + Tables.DIRECTORIES +
+ " WHERE " + Directory.ACCOUNT_NAME + "=?" +
+ " AND " + Directory.ACCOUNT_TYPE + "=?", params);
+ resetDirectoryCache();
+ }
+
// Find all aggregated contacts that used to contain the raw contacts
// we have just deleted and see if they are still referencing the deleted
- // names of photos. If so, fix up those contacts.
+ // names or photos. If so, fix up those contacts.
HashSet<Long> orphanContactIds = Sets.newHashSet();
Cursor cursor = mDb.rawQuery("SELECT " + Contacts._ID +
" FROM " + Tables.CONTACTS +
@@ -4083,83 +4287,34 @@
for (Long contactId : orphanContactIds) {
mContactAggregator.updateAggregateData(contactId);
}
+ mDbHelper.updateAllVisible();
}
- if (hasUnassignedContacts[0]) {
-
- Account primaryAccount = null;
- for (Account account : accounts) {
- if (isWritableAccount(account.type)) {
- primaryAccount = account;
- break;
- }
- }
-
- if (primaryAccount != null) {
- String[] params = new String[] {primaryAccount.name, primaryAccount.type};
- if (primaryAccount.type.equals(DEFAULT_ACCOUNT_TYPE)) {
- long groupId = getOrCreateMyContactsGroupInTransaction(
- primaryAccount.name, primaryAccount.type);
- if (groupId != -1) {
- long mimeTypeId = mDbHelper.getMimeTypeId(
- GroupMembership.CONTENT_ITEM_TYPE);
- mDb.execSQL(
- "INSERT INTO " + Tables.DATA + "(" + DataColumns.MIMETYPE_ID +
- ", " + Data.RAW_CONTACT_ID + ", "
- + GroupMembership.GROUP_ROW_ID + ") " +
- "SELECT " + mimeTypeId + ", "
- + RawContacts._ID + ", " + groupId +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.ACCOUNT_NAME + " IS NULL" +
- " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL"
- );
- }
- }
- mDb.execSQL(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.ACCOUNT_NAME + "=?,"
- + RawContacts.ACCOUNT_TYPE + "=?" +
- " WHERE " + RawContacts.ACCOUNT_NAME + " IS NULL" +
- " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL", params);
-
- // We don't currently support groups for unsynced accounts, so this is for
- // the future
- mDb.execSQL(
- "UPDATE " + Tables.GROUPS +
- " SET " + Groups.ACCOUNT_NAME + "=?,"
- + Groups.ACCOUNT_TYPE + "=?" +
- " WHERE " + Groups.ACCOUNT_NAME + " IS NULL" +
- " AND " + Groups.ACCOUNT_TYPE + " IS NULL", params);
-
- mDb.execSQL(
- "DELETE FROM " + Tables.ACCOUNTS +
- " WHERE " + RawContacts.ACCOUNT_NAME + " IS NULL" +
- " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL");
- }
+ if (accountsChanged) {
+ mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
}
-
- mDbHelper.updateAllVisible();
-
- mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
mAccountWritability.clear();
+ return accountsChanged;
+ }
+
+ public void onPackageChanged(String packageName) {
+ mContactDirectoryManager.onPackageChanged(packageName);
}
/**
* Finds all distinct accounts present in the specified table.
*/
- private void findValidAccounts(Set<Account> validAccounts, boolean[] hasUnassignedContacts) {
+ private void findValidAccounts(Set<Account> validAccounts) {
Cursor c = mDb.rawQuery(
"SELECT " + RawContacts.ACCOUNT_NAME + "," + RawContacts.ACCOUNT_TYPE +
" FROM " + Tables.ACCOUNTS, null);
try {
while (c.moveToNext()) {
- if (c.isNull(0) && c.isNull(1)) {
- hasUnassignedContacts[0] = true;
- } else {
+ if (!c.isNull(0) || !c.isNull(1)) {
validAccounts.add(new Account(c.getString(0), c.getString(1)));
}
}
@@ -4195,6 +4350,104 @@
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ String directory = getQueryParameter(uri, ContactsContract.DIRECTORY_PARAM_KEY);
+ if (directory == null) {
+ return queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1);
+ } else if (directory.equals("0")) {
+ return queryLocal(uri, projection, selection, selectionArgs, sortOrder,
+ Directory.DEFAULT);
+ } else if (directory.equals("1")) {
+ return queryLocal(uri, projection, selection, selectionArgs, sortOrder,
+ Directory.LOCAL_INVISIBLE);
+ }
+
+ DirectoryInfo directoryInfo = getDirectoryAuthority(directory);
+ if (directoryInfo == null) {
+ throw new IllegalArgumentException(
+ mDbHelper.exceptionMessage("Invalid directory ID", uri));
+ }
+
+ Builder builder = new Uri.Builder();
+ builder.scheme(ContentResolver.SCHEME_CONTENT);
+ builder.authority(directoryInfo.authority);
+ builder.encodedPath(uri.getEncodedPath());
+ if (directoryInfo.accountName != null) {
+ builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, directoryInfo.accountName);
+ }
+ if (directoryInfo.accountType != null) {
+ builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, directoryInfo.accountType);
+ }
+
+ String limit = getLimit(uri);
+ if (limit != null) {
+ builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, limit);
+ }
+
+ Uri directoryUri = builder.build();
+
+ if (projection == null) {
+ projection = getDefaultProjection(uri);
+ }
+
+ Cursor cursor = getContext().getContentResolver().query(directoryUri, projection, selection,
+ selectionArgs, sortOrder);
+ while (cursor instanceof CursorWrapper) {
+ cursor = ((CursorWrapper)cursor).getWrappedCursor();
+ }
+ return cursor;
+ }
+
+ private static final class DirectoryQuery {
+ public static final String[] COLUMNS = new String[] {
+ Directory._ID,
+ Directory.DIRECTORY_AUTHORITY,
+ Directory.ACCOUNT_NAME,
+ Directory.ACCOUNT_TYPE
+ };
+
+ public static final int DIRECTORY_ID = 0;
+ public static final int AUTHORITY = 1;
+ public static final int ACCOUNT_NAME = 2;
+ public static final int ACCOUNT_TYPE = 3;
+ }
+
+ /**
+ * Reads and caches directory information for the database.
+ */
+ private DirectoryInfo getDirectoryAuthority(String directoryId) {
+ synchronized (mDirectoryCache) {
+ if (!mDirectoryCacheValid) {
+ mDirectoryCache.clear();
+ Cursor cursor = mDb.query(Tables.DIRECTORIES,
+ DirectoryQuery.COLUMNS,
+ null, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ DirectoryInfo info = new DirectoryInfo();
+ String id = cursor.getString(DirectoryQuery.DIRECTORY_ID);
+ info.authority = cursor.getString(DirectoryQuery.AUTHORITY);
+ info.accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
+ info.accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
+ mDirectoryCache.put(id, info);
+ }
+ } finally {
+ cursor.close();
+ }
+ mDirectoryCacheValid = true;
+ }
+
+ return mDirectoryCache.get(directoryId);
+ }
+ }
+
+ public void resetDirectoryCache() {
+ synchronized(mDirectoryCache) {
+ mDirectoryCacheValid = false;
+ }
+ }
+
+ public Cursor queryLocal(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder, long directoryId) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "query: " + uri);
}
@@ -4215,6 +4468,7 @@
case CONTACTS: {
setTablesAndProjectionMapForContacts(qb, uri, projection);
+ appendLocalDirectorySelectionIfNeeded(qb, directoryId);
break;
}
@@ -4234,29 +4488,19 @@
throw new IllegalArgumentException(mDbHelper.exceptionMessage(
"Missing a lookup key", uri));
}
+
String lookupKey = pathSegments.get(2);
if (segmentCount == 4) {
- // TODO: pull this out into a method and generalize to not require contactId
long contactId = Long.parseLong(pathSegments.get(3));
SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
setTablesAndProjectionMapForContacts(lookupQb, uri, projection);
- String[] args;
- if (selectionArgs == null) {
- args = new String[2];
- } else {
- args = new String[selectionArgs.length + 2];
- System.arraycopy(selectionArgs, 0, args, 2, selectionArgs.length);
- }
- args[0] = String.valueOf(contactId);
- args[1] = Uri.encode(lookupKey);
- lookupQb.appendWhere(Contacts._ID + "=? AND " + Contacts.LOOKUP_KEY + "=?");
- Cursor c = query(db, lookupQb, projection, selection, args, sortOrder,
- groupBy, limit);
- if (c.getCount() != 0) {
+
+ Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
+ projection, selection, selectionArgs, sortOrder, groupBy, limit,
+ Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey);
+ if (c != null) {
return c;
}
-
- c.close();
}
setTablesAndProjectionMapForContacts(qb, uri, projection);
@@ -4266,6 +4510,37 @@
break;
}
+ case CONTACTS_LOOKUP_DATA:
+ case CONTACTS_LOOKUP_ID_DATA: {
+ List<String> pathSegments = uri.getPathSegments();
+ int segmentCount = pathSegments.size();
+ if (segmentCount < 4) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Missing a lookup key", uri));
+ }
+ String lookupKey = pathSegments.get(2);
+ if (segmentCount == 5) {
+ long contactId = Long.parseLong(pathSegments.get(3));
+ SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
+ setTablesAndProjectionMapForData(lookupQb, uri, projection, false);
+ lookupQb.appendWhere(" AND ");
+ Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
+ projection, selection, selectionArgs, sortOrder, groupBy, limit,
+ Data.CONTACT_ID, contactId, Data.LOOKUP_KEY, lookupKey);
+ if (c != null) {
+ return c;
+ }
+
+ // TODO see if the contact exists but has no data rows (rare)
+ }
+
+ setTablesAndProjectionMapForData(qb, uri, projection, false);
+ selectionArgs = insertSelectionArg(selectionArgs,
+ String.valueOf(lookupContactIdByLookupKey(db, lookupKey)));
+ qb.appendWhere(" AND " + Data.CONTACT_ID + "=?");
+ break;
+ }
+
case CONTACTS_AS_VCARD: {
// When reading as vCard always use restricted view
final String lookupKey = Uri.encode(uri.getPathSegments().get(2));
@@ -4293,6 +4568,7 @@
filterParam = uri.getLastPathSegment();
}
setTablesAndProjectionMapForContactsWithSnippet(qb, uri, projection, filterParam);
+ appendLocalDirectorySelectionIfNeeded(qb, directoryId);
break;
}
@@ -4313,8 +4589,10 @@
String[] starredProjection = null;
String[] frequentProjection = null;
if (projection != null) {
- starredProjection = appendProjectionArg(projection, TIMES_CONTACED_SORT_COLUMN);
- frequentProjection = appendProjectionArg(projection, TIMES_CONTACED_SORT_COLUMN);
+ starredProjection =
+ appendProjectionArg(projection, TIMES_CONTACTED_SORT_COLUMN);
+ frequentProjection =
+ appendProjectionArg(projection, TIMES_CONTACTED_SORT_COLUMN);
}
// Build the first query for starred
@@ -4357,7 +4635,7 @@
break;
}
- case CONTACTS_DATA: {
+ case CONTACTS_ID_DATA: {
long contactId = Long.parseLong(uri.getPathSegments().get(1));
setTablesAndProjectionMapForData(qb, uri, projection, false);
selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(contactId));
@@ -4365,7 +4643,7 @@
break;
}
- case CONTACTS_PHOTO: {
+ case CONTACTS_ID_PHOTO: {
long contactId = Long.parseLong(uri.getPathSegments().get(1));
setTablesAndProjectionMapForData(qb, uri, projection, false);
selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(contactId));
@@ -4374,6 +4652,45 @@
break;
}
+ case CONTACTS_ID_ENTITIES: {
+ long contactId = Long.parseLong(uri.getPathSegments().get(1));
+ setTablesAndProjectionMapForEntities(qb, uri, projection);
+ selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(contactId));
+ qb.appendWhere(" AND " + RawContacts.CONTACT_ID + "=?");
+ break;
+ }
+
+ case CONTACTS_LOOKUP_ENTITIES:
+ case CONTACTS_LOOKUP_ID_ENTITIES: {
+ List<String> pathSegments = uri.getPathSegments();
+ int segmentCount = pathSegments.size();
+ if (segmentCount < 4) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Missing a lookup key", uri));
+ }
+ String lookupKey = pathSegments.get(2);
+ if (segmentCount == 5) {
+ long contactId = Long.parseLong(pathSegments.get(3));
+ SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
+ setTablesAndProjectionMapForEntities(lookupQb, uri, projection);
+ lookupQb.appendWhere(" AND ");
+
+ Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
+ projection, selection, selectionArgs, sortOrder, groupBy, limit,
+ Contacts.Entity.CONTACT_ID, contactId,
+ Contacts.Entity.LOOKUP_KEY, lookupKey);
+ if (c != null) {
+ return c;
+ }
+ }
+
+ setTablesAndProjectionMapForEntities(qb, uri, projection);
+ selectionArgs = insertSelectionArg(selectionArgs,
+ String.valueOf(lookupContactIdByLookupKey(db, lookupKey)));
+ qb.appendWhere(" AND " + Contacts.Entity.CONTACT_ID + "=?");
+ break;
+ }
+
case PHONES: {
setTablesAndProjectionMapForData(qb, uri, projection, false);
qb.appendWhere(" AND " + Data.MIMETYPE + " = '" + Phone.CONTENT_ITEM_TYPE + "'");
@@ -4406,18 +4723,17 @@
hasCondition = true;
}
- if (isPhoneNumber(filterParam)) {
+ String number = PhoneNumberUtils.normalizeNumber(filterParam);
+ if (!TextUtils.isEmpty(number)) {
if (orNeeded) {
sb.append(" OR ");
}
- String number = PhoneNumberUtils.convertKeypadLettersToDigits(filterParam);
- String reversed = PhoneNumberUtils.getStrippedReversed(number);
sb.append(Data._ID +
- " IN (SELECT " + PhoneLookupColumns.DATA_ID
- + " FROM " + Tables.PHONE_LOOKUP
- + " WHERE " + PhoneLookupColumns.NORMALIZED_NUMBER + " LIKE '%");
- sb.append(reversed);
- sb.append("')");
+ " IN (SELECT DISTINCT " + PhoneLookupColumns.DATA_ID
+ + " FROM " + Tables.PHONE_LOOKUP
+ + " WHERE " + PhoneLookupColumns.NORMALIZED_NUMBER + " LIKE '");
+ sb.append(number);
+ sb.append("%')");
hasCondition = true;
}
@@ -4509,7 +4825,7 @@
}
groupBy = Email.DATA + "," + RawContacts.CONTACT_ID;
if (sortOrder == null) {
- sortOrder = Contacts.IN_VISIBLE_GROUP + " DESC, " + RawContacts.CONTACT_ID;
+ sortOrder = EMAIL_FILTER_SORT_ORDER;
}
break;
}
@@ -4568,13 +4884,16 @@
if (TextUtils.isEmpty(sortOrder)) {
// Default the sort order to something reasonable so we get consistent
// results when callers don't request an ordering
- sortOrder = RawContactsColumns.CONCRETE_ID;
+ sortOrder = " length(lookup.normalized_number) DESC";
}
String number = uri.getPathSegments().size() > 1 ? uri.getLastPathSegment() : "";
- mDbHelper.buildPhoneLookupAndContactQuery(qb, number);
+ String numberE164 =
+ PhoneNumberUtils.formatNumberToE164(number, getCurrentCountryIso());
+ String normalizedNumber =
+ PhoneNumberUtils.normalizeNumber(number);
+ mDbHelper.buildPhoneLookupAndContactQuery(qb, normalizedNumber, numberE164);
qb.setProjectionMap(sPhoneLookupProjectionMap);
-
// Phone lookup cannot be combined with a selection
selection = null;
selectionArgs = null;
@@ -4623,10 +4942,26 @@
maxSuggestions = DEFAULT_MAX_SUGGESTIONS;
}
+ ArrayList<AggregationSuggestionParameter> parameters = null;
+ List<String> query = uri.getQueryParameters("query");
+ if (query != null && !query.isEmpty()) {
+ parameters = new ArrayList<AggregationSuggestionParameter>(query.size());
+ for (String parameter : query) {
+ int offset = parameter.indexOf(':');
+ parameters.add(offset == -1
+ ? new AggregationSuggestionParameter(
+ AggregationSuggestions.PARAMETER_MATCH_NAME,
+ parameter)
+ : new AggregationSuggestionParameter(
+ parameter.substring(0, offset),
+ parameter.substring(offset + 1)));
+ }
+ }
+
setTablesAndProjectionMapForContacts(qb, uri, projection);
return mContactAggregator.queryAggregationSuggestions(qb, projection, contactId,
- maxSuggestions, filter);
+ maxSuggestions, filter, parameters);
}
case SETTINGS: {
@@ -4696,13 +5031,13 @@
break;
case RAW_CONTACT_ENTITIES: {
- setTablesAndProjectionMapForRawContactsEntities(qb, uri);
+ setTablesAndProjectionMapForRawEntities(qb, uri);
break;
}
case RAW_CONTACT_ENTITY_ID: {
long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
- setTablesAndProjectionMapForRawContactsEntities(qb, uri);
+ setTablesAndProjectionMapForRawEntities(qb, uri);
selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(rawContactId));
qb.appendWhere(" AND " + RawContacts._ID + "=?");
break;
@@ -4712,6 +5047,25 @@
return queryProviderStatus(uri, projection);
}
+ case DIRECTORIES : {
+ qb.setTables(Tables.DIRECTORIES);
+ qb.setProjectionMap(sDirectoryProjectionMap);
+ break;
+ }
+
+ case DIRECTORIES_ID : {
+ long id = ContentUris.parseId(uri);
+ qb.setTables(Tables.DIRECTORIES);
+ qb.setProjectionMap(sDirectoryProjectionMap);
+ selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(id));
+ qb.appendWhere(Directory._ID + "=?");
+ break;
+ }
+
+ case COMPLETE_NAME: {
+ return completeName(uri, projection);
+ }
+
default:
return mLegacyApiSupport.query(uri, projection, selection, selectionArgs,
sortOrder, limit);
@@ -4758,6 +5112,35 @@
return cursor;
}
+ /**
+ * Runs the query with the supplied contact ID and lookup ID. If the query succeeds,
+ * it returns the resulting cursor, otherwise it returns null and the calling
+ * method needs to resolve the lookup key and rerun the query.
+ */
+ private Cursor queryWithContactIdAndLookupKey(SQLiteQueryBuilder lookupQb,
+ SQLiteDatabase db, Uri uri,
+ String[] projection, String selection, String[] selectionArgs,
+ String sortOrder, String groupBy, String limit,
+ String contactIdColumn, long contactId, String lookupKeyColumn, String lookupKey) {
+ String[] args;
+ if (selectionArgs == null) {
+ args = new String[2];
+ } else {
+ args = new String[selectionArgs.length + 2];
+ System.arraycopy(selectionArgs, 0, args, 2, selectionArgs.length);
+ }
+ args[0] = String.valueOf(contactId);
+ args[1] = Uri.encode(lookupKey);
+ lookupQb.appendWhere(contactIdColumn + "=? AND " + lookupKeyColumn + "=?");
+ Cursor c = query(db, lookupQb, projection, selection, args, sortOrder,
+ groupBy, limit);
+ if (c.getCount() != 0) {
+ return c;
+ }
+
+ c.close();
+ return null;
+ }
private static final class AddressBookIndexQuery {
public static final String LETTER = "letter";
@@ -5176,25 +5559,30 @@
") AS " + SearchSnippetColumns.SNIPPET_MIMETYPE);
}
- sb.append(" FROM " + Tables.DATA_JOIN_RAW_CONTACTS +
- " WHERE " + DataColumns.CONCRETE_ID +
- " IN (");
+ sb.append(" FROM " + Tables.DATA_JOIN_RAW_CONTACTS + " WHERE ");
- // Construct a query that gives us exactly one data _id per matching contact.
- // MIN stands in for ANY in this context.
- sb.append(
- "SELECT MIN(" + Tables.NAME_LOOKUP + "." + NameLookupColumns.DATA_ID + ")" +
- " FROM " + Tables.NAME_LOOKUP +
- " JOIN " + Tables.RAW_CONTACTS +
- " ON (" + RawContactsColumns.CONCRETE_ID
- + "=" + Tables.NAME_LOOKUP + "." + NameLookupColumns.RAW_CONTACT_ID + ")" +
- " WHERE " + NameLookupColumns.NORMALIZED_NAME + " GLOB '");
- sb.append(NameNormalizer.normalize(filter));
- sb.append("*' AND " + NameLookupColumns.NAME_TYPE +
- " IN(" + CONTACT_LOOKUP_NAME_TYPES + ")" +
- " GROUP BY " + RawContactsColumns.CONCRETE_CONTACT_ID);
+ if (!TextUtils.isEmpty(filter)) {
+ sb.append(DataColumns.CONCRETE_ID + " IN (");
- sb.append(")) ON (" + Contacts._ID + "=snippet_contact_id)");
+ // Construct a query that gives us exactly one data _id per matching contact.
+ // MIN stands in for ANY in this context.
+ sb.append(
+ "SELECT MIN(" + Tables.NAME_LOOKUP + "." + NameLookupColumns.DATA_ID + ")" +
+ " FROM " + Tables.NAME_LOOKUP +
+ " JOIN " + Tables.RAW_CONTACTS +
+ " ON (" + RawContactsColumns.CONCRETE_ID
+ + "=" + Tables.NAME_LOOKUP + "." + NameLookupColumns.RAW_CONTACT_ID + ")" +
+ " WHERE " + NameLookupColumns.NORMALIZED_NAME + " GLOB '");
+ sb.append(NameNormalizer.normalize(filter));
+ sb.append("*' AND " + NameLookupColumns.NAME_TYPE +
+ " IN(" + CONTACT_LOOKUP_NAME_TYPES + ")" +
+ " GROUP BY " + RawContactsColumns.CONCRETE_CONTACT_ID +
+ ")");
+ } else {
+ sb.append("0"); // Empty filter - return an empty set
+ }
+
+ sb.append(") ON (" + Contacts._ID + "=snippet_contact_id)");
qb.setTables(sb.toString());
qb.setProjectionMap(sContactsProjectionWithSnippetMap);
@@ -5208,22 +5596,8 @@
excludeRestrictedData = !mDbHelper.hasAccessToRestrictedData(requestingPackage);
}
sb.append(mDbHelper.getContactView(excludeRestrictedData));
- if (mDbHelper.isInProjection(projection,
- Contacts.CONTACT_PRESENCE)) {
- sb.append(" LEFT OUTER JOIN " + Tables.AGGREGATED_PRESENCE +
- " ON (" + Contacts._ID + " = " + AggregatedPresenceColumns.CONTACT_ID + ")");
- }
- if (mDbHelper.isInProjection(projection,
- Contacts.CONTACT_STATUS,
- Contacts.CONTACT_STATUS_RES_PACKAGE,
- Contacts.CONTACT_STATUS_ICON,
- Contacts.CONTACT_STATUS_LABEL,
- Contacts.CONTACT_STATUS_TIMESTAMP)) {
- sb.append(" LEFT OUTER JOIN " + Tables.STATUS_UPDATES + " "
- + ContactsStatusUpdatesColumns.ALIAS +
- " ON (" + ContactsColumns.LAST_STATUS_UPDATE_ID + "="
- + ContactsStatusUpdatesColumns.CONCRETE_DATA_ID + ")");
- }
+ appendContactPresenceJoin(sb, projection, Contacts._ID);
+ appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
}
private void setTablesAndProjectionMapForRawContacts(SQLiteQueryBuilder qb, Uri uri) {
@@ -5240,80 +5614,29 @@
appendAccountFromParameter(qb, uri);
}
- private void setTablesAndProjectionMapForRawContactsEntities(SQLiteQueryBuilder qb, Uri uri) {
- // Note: currently, "export only" equals to "restricted", but may not in the future.
- boolean excludeRestrictedData = readBooleanQueryParameter(uri,
- Data.FOR_EXPORT_ONLY, false);
-
- String requestingPackage = getQueryParameter(uri,
- ContactsContract.REQUESTING_PACKAGE_PARAM_KEY);
- if (requestingPackage != null) {
- excludeRestrictedData = excludeRestrictedData
- || !mDbHelper.hasAccessToRestrictedData(requestingPackage);
- }
- qb.setTables(mDbHelper.getContactEntitiesView(excludeRestrictedData));
- qb.setProjectionMap(sRawContactsEntityProjectionMap);
+ private void setTablesAndProjectionMapForRawEntities(SQLiteQueryBuilder qb, Uri uri) {
+ qb.setTables(mDbHelper.getRawEntitiesView(shouldExcludeRestrictedData(uri)));
+ qb.setProjectionMap(sRawEntityProjectionMap);
appendAccountFromParameter(qb, uri);
}
private void setTablesAndProjectionMapForData(SQLiteQueryBuilder qb, Uri uri,
String[] projection, boolean distinct) {
StringBuilder sb = new StringBuilder();
- // Note: currently, "export only" equals to "restricted", but may not in the future.
- boolean excludeRestrictedData = readBooleanQueryParameter(uri,
- Data.FOR_EXPORT_ONLY, false);
-
- String requestingPackage = getQueryParameter(uri,
- ContactsContract.REQUESTING_PACKAGE_PARAM_KEY);
- if (requestingPackage != null) {
- excludeRestrictedData = excludeRestrictedData
- || !mDbHelper.hasAccessToRestrictedData(requestingPackage);
- }
-
- sb.append(mDbHelper.getDataView(excludeRestrictedData));
+ sb.append(mDbHelper.getDataView(shouldExcludeRestrictedData(uri)));
sb.append(" data");
- // Include aggregated presence when requested
- if (mDbHelper.isInProjection(projection, Data.CONTACT_PRESENCE)) {
- sb.append(" LEFT OUTER JOIN " + Tables.AGGREGATED_PRESENCE +
- " ON (" + AggregatedPresenceColumns.CONCRETE_CONTACT_ID + "="
- + RawContacts.CONTACT_ID + ")");
- }
-
- // Include aggregated status updates when requested
- if (mDbHelper.isInProjection(projection,
- Data.CONTACT_STATUS,
- Data.CONTACT_STATUS_RES_PACKAGE,
- Data.CONTACT_STATUS_ICON,
- Data.CONTACT_STATUS_LABEL,
- Data.CONTACT_STATUS_TIMESTAMP)) {
- sb.append(" LEFT OUTER JOIN " + Tables.STATUS_UPDATES + " "
- + ContactsStatusUpdatesColumns.ALIAS +
- " ON (" + ContactsColumns.LAST_STATUS_UPDATE_ID + "="
- + ContactsStatusUpdatesColumns.CONCRETE_DATA_ID + ")");
- }
-
- // Include individual presence when requested
- if (mDbHelper.isInProjection(projection, Data.PRESENCE)) {
- sb.append(" LEFT OUTER JOIN " + Tables.PRESENCE +
- " ON (" + StatusUpdates.DATA_ID + "="
- + DataColumns.CONCRETE_ID + ")");
- }
-
- // Include individual status updates when requested
- if (mDbHelper.isInProjection(projection,
- Data.STATUS,
- Data.STATUS_RES_PACKAGE,
- Data.STATUS_ICON,
- Data.STATUS_LABEL,
- Data.STATUS_TIMESTAMP)) {
- sb.append(" LEFT OUTER JOIN " + Tables.STATUS_UPDATES +
- " ON (" + StatusUpdatesColumns.CONCRETE_DATA_ID + "="
- + DataColumns.CONCRETE_ID + ")");
- }
+ appendContactPresenceJoin(sb, projection, RawContacts.CONTACT_ID);
+ appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
+ appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID);
+ appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID);
qb.setTables(sb.toString());
- qb.setProjectionMap(distinct ? sDistinctDataProjectionMap : sDataProjectionMap);
+
+ boolean useDistinct = distinct
+ || !mDbHelper.isInProjection(projection, DISTINCT_DATA_PROHIBITING_COLUMNS);
+ qb.setDistinct(useDistinct);
+ qb.setProjectionMap(useDistinct ? sDistinctDataProjectionMap : sDataProjectionMap);
appendAccountFromParameter(qb, uri);
}
@@ -5322,13 +5645,46 @@
StringBuilder sb = new StringBuilder();
sb.append(mDbHelper.getDataView());
sb.append(" data");
+ appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID);
+ appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID);
- if (mDbHelper.isInProjection(projection, StatusUpdates.PRESENCE)) {
- sb.append(" LEFT OUTER JOIN " + Tables.PRESENCE +
- " ON(" + Tables.PRESENCE + "." + StatusUpdates.DATA_ID
- + "=" + DataColumns.CONCRETE_ID + ")");
+ qb.setTables(sb.toString());
+ qb.setProjectionMap(sStatusUpdatesProjectionMap);
+ }
+
+ private void setTablesAndProjectionMapForEntities(SQLiteQueryBuilder qb, Uri uri,
+ String[] projection) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mDbHelper.getEntitiesView(shouldExcludeRestrictedData(uri)));
+ sb.append(" data");
+
+ appendContactPresenceJoin(sb, projection, Contacts.Entity.CONTACT_ID);
+ appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
+ appendDataPresenceJoin(sb, projection, Contacts.Entity.DATA_ID);
+ appendDataStatusUpdateJoin(sb, projection, Contacts.Entity.DATA_ID);
+
+ qb.setTables(sb.toString());
+ qb.setProjectionMap(sEntityProjectionMap);
+ appendAccountFromParameter(qb, uri);
+ }
+
+ private void appendContactStatusUpdateJoin(StringBuilder sb, String[] projection,
+ String lastStatusUpdateIdColumn) {
+ if (mDbHelper.isInProjection(projection,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_ICON,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_TIMESTAMP)) {
+ sb.append(" LEFT OUTER JOIN " + Tables.STATUS_UPDATES + " "
+ + ContactsStatusUpdatesColumns.ALIAS +
+ " ON (" + lastStatusUpdateIdColumn + "="
+ + ContactsStatusUpdatesColumns.CONCRETE_DATA_ID + ")");
}
+ }
+ private void appendDataStatusUpdateJoin(StringBuilder sb, String[] projection,
+ String dataIdColumn) {
if (mDbHelper.isInProjection(projection,
StatusUpdates.STATUS,
StatusUpdates.STATUS_RES_PACKAGE,
@@ -5336,11 +5692,52 @@
StatusUpdates.STATUS_LABEL,
StatusUpdates.STATUS_TIMESTAMP)) {
sb.append(" LEFT OUTER JOIN " + Tables.STATUS_UPDATES +
- " ON(" + Tables.STATUS_UPDATES + "." + StatusUpdatesColumns.DATA_ID
- + "=" + DataColumns.CONCRETE_ID + ")");
+ " ON (" + StatusUpdatesColumns.CONCRETE_DATA_ID + "="
+ + dataIdColumn + ")");
}
- qb.setTables(sb.toString());
- qb.setProjectionMap(sStatusUpdatesProjectionMap);
+ }
+
+ private void appendContactPresenceJoin(StringBuilder sb, String[] projection,
+ String contactIdColumn) {
+ if (mDbHelper.isInProjection(projection,
+ Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY)) {
+ sb.append(" LEFT OUTER JOIN " + Tables.AGGREGATED_PRESENCE +
+ " ON (" + contactIdColumn + " = "
+ + AggregatedPresenceColumns.CONCRETE_CONTACT_ID + ")");
+ }
+ }
+
+ private void appendDataPresenceJoin(StringBuilder sb, String[] projection,
+ String dataIdColumn) {
+ if (mDbHelper.isInProjection(projection, Data.PRESENCE, Data.CHAT_CAPABILITY)) {
+ sb.append(" LEFT OUTER JOIN " + Tables.PRESENCE +
+ " ON (" + StatusUpdates.DATA_ID + "=" + dataIdColumn + ")");
+ }
+ }
+
+ private void appendLocalDirectorySelectionIfNeeded(SQLiteQueryBuilder qb, long directoryId) {
+ if (directoryId == Directory.DEFAULT) {
+ qb.appendWhere(Contacts._ID + " IN " + Tables.DEFAULT_DIRECTORY);
+ } else if (directoryId == Directory.LOCAL_INVISIBLE){
+ qb.appendWhere(Contacts._ID + " NOT IN " + Tables.DEFAULT_DIRECTORY);
+ }
+ }
+
+ private boolean shouldExcludeRestrictedData(Uri uri) {
+ // Note: currently, "export only" equals to "restricted", but may not in the future.
+ boolean excludeRestrictedData = readBooleanQueryParameter(uri,
+ Data.FOR_EXPORT_ONLY, false);
+ if (excludeRestrictedData) {
+ return true;
+ }
+
+ String requestingPackage = getQueryParameter(uri,
+ ContactsContract.REQUESTING_PACKAGE_PARAM_KEY);
+ if (requestingPackage != null) {
+ return !mDbHelper.hasAccessToRestrictedData(requestingPackage);
+ }
+
+ return false;
}
private void appendAccountFromParameter(SQLiteQueryBuilder qb, Uri uri) {
@@ -5404,7 +5801,7 @@
* the parameter is not set, or is set to an invalid value.
*/
private String getLimit(Uri uri) {
- String limitParam = getQueryParameter(uri, "limit");
+ String limitParam = getQueryParameter(uri, ContactsContract.LIMIT_PARAM_KEY);
if (limitParam == null) {
return null;
}
@@ -5422,36 +5819,6 @@
}
}
- /**
- * Returns true if all the characters are meaningful as digits
- * in a phone number -- letters, digits, and a few punctuation marks.
- */
- private boolean isPhoneNumber(CharSequence cons) {
- int len = cons.length();
-
- for (int i = 0; i < len; i++) {
- char c = cons.charAt(i);
-
- if ((c >= '0') && (c <= '9')) {
- continue;
- }
- if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+')
- || (c == '#') || (c == '*')) {
- continue;
- }
- if ((c >= 'A') && (c <= 'Z')) {
- continue;
- }
- if ((c >= 'a') && (c <= 'z')) {
- continue;
- }
-
- return false;
- }
-
- return true;
- }
-
String getContactsRestrictions() {
if (mDbHelper.hasAccessToRestrictedData()) {
return "1";
@@ -5470,17 +5837,17 @@
}
@Override
- public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
int match = sUriMatcher.match(uri);
switch (match) {
- case CONTACTS_PHOTO: {
- return openPhotoAssetFile(uri, mode,
+ case CONTACTS_ID_PHOTO: {
+ return openPhotoFile(uri, mode,
Data._ID + "=" + Contacts.PHOTO_ID + " AND " + RawContacts.CONTACT_ID + "=?",
new String[]{uri.getPathSegments().get(1)});
}
case DATA_ID: {
- return openPhotoAssetFile(uri, mode,
+ return openPhotoFile(uri, mode,
Data._ID + "=? AND " + Data.MIMETYPE + "='" + Photo.CONTENT_ITEM_TYPE + "'",
new String[]{uri.getPathSegments().get(1)});
}
@@ -5495,7 +5862,7 @@
// then pipe into MemoryFile once the exact size is known.
final ByteArrayOutputStream localStream = new ByteArrayOutputStream();
outputRawContactsAsVCard(localStream, selection, mSelectionArgs1);
- return buildAssetFileDescriptor(localStream);
+ return buildFileDescriptor(localStream);
}
case CONTACTS_AS_MULTI_VCARD: {
@@ -5522,7 +5889,7 @@
// then pipe into MemoryFile once the exact size is known.
final ByteArrayOutputStream localStream = new ByteArrayOutputStream();
outputRawContactsAsVCard(localStream, selection, null);
- return buildAssetFileDescriptor(localStream);
+ return buildFileDescriptor(localStream);
}
default:
@@ -5531,7 +5898,7 @@
}
}
- private AssetFileDescriptor openPhotoAssetFile(Uri uri, String mode, String selection,
+ private ParcelFileDescriptor openPhotoFile(Uri uri, String mode, String selection,
String[] selectionArgs)
throws FileNotFoundException {
if (!"r".equals(mode)) {
@@ -5543,33 +5910,26 @@
"SELECT " + Photo.PHOTO + " FROM " + mDbHelper.getDataView() +
" WHERE " + selection;
SQLiteDatabase db = mDbHelper.getReadableDatabase();
- return SQLiteContentHelper.getBlobColumnAsAssetFile(db, sql,
- selectionArgs);
+ return DatabaseUtils.blobFileDescriptorForQuery(db, sql, selectionArgs);
}
private static final String CONTACT_MEMORY_FILE_NAME = "contactAssetFile";
/**
- * Build a {@link AssetFileDescriptor} through a {@link MemoryFile} with the
+ * Returns a {@link ParcelFileDescriptor} backed by the
* contents of the given {@link ByteArrayOutputStream}.
*/
- private AssetFileDescriptor buildAssetFileDescriptor(ByteArrayOutputStream stream) {
- AssetFileDescriptor fd = null;
+ private ParcelFileDescriptor buildFileDescriptor(ByteArrayOutputStream stream) {
try {
stream.flush();
final byte[] byteData = stream.toByteArray();
- final int size = byteData.length;
- final MemoryFile memoryFile = new MemoryFile(CONTACT_MEMORY_FILE_NAME, size);
- memoryFile.writeBytes(byteData, 0, 0, size);
- memoryFile.deactivate();
-
- fd = AssetFileDescriptor.fromMemoryFile(memoryFile);
+ return ParcelFileDescriptor.fromData(byteData, CONTACT_MEMORY_FILE_NAME);
} catch (IOException e) {
- Log.w(TAG, "Problem writing stream into an AssetFileDescriptor: " + e.toString());
+ Log.w(TAG, "Problem writing stream into an ParcelFileDescriptor: " + e.toString());
+ return null;
}
- return fd;
}
/**
@@ -5611,6 +5971,8 @@
case CONTACTS_AS_VCARD:
case CONTACTS_AS_MULTI_VCARD:
return Contacts.CONTENT_VCARD_TYPE;
+ case CONTACTS_ID_PHOTO:
+ return "image/png";
case RAW_CONTACTS:
return RawContacts.CONTENT_TYPE;
case RAW_CONTACTS_ID:
@@ -5645,12 +6007,64 @@
return SearchManager.SUGGEST_MIME_TYPE;
case SEARCH_SHORTCUT:
return SearchManager.SHORTCUT_MIME_TYPE;
-
+ case DIRECTORIES:
+ return Directory.CONTENT_TYPE;
+ case DIRECTORIES_ID:
+ return Directory.CONTENT_ITEM_TYPE;
default:
return mLegacyApiSupport.getType(uri);
}
}
+ public String[] getDefaultProjection(Uri uri) {
+ final int match = sUriMatcher.match(uri);
+ switch (match) {
+ case CONTACTS:
+ case CONTACTS_LOOKUP:
+ case CONTACTS_ID:
+ case CONTACTS_LOOKUP_ID:
+ case AGGREGATION_SUGGESTIONS:
+ return sContactsProjectionMap.getColumnNames();
+
+ case CONTACTS_ID_ENTITIES:
+ return sEntityProjectionMap.getColumnNames();
+
+ case CONTACTS_AS_VCARD:
+ case CONTACTS_AS_MULTI_VCARD:
+ return sContactsVCardProjectionMap.getColumnNames();
+
+ case RAW_CONTACTS:
+ case RAW_CONTACTS_ID:
+ return sRawContactsProjectionMap.getColumnNames();
+
+ case DATA_ID:
+ case PHONES:
+ case PHONES_ID:
+ case EMAILS:
+ case EMAILS_ID:
+ case POSTALS:
+ case POSTALS_ID:
+ return sDataProjectionMap.getColumnNames();
+
+ case PHONE_LOOKUP:
+ return sPhoneLookupProjectionMap.getColumnNames();
+
+ case AGGREGATION_EXCEPTIONS:
+ case AGGREGATION_EXCEPTION_ID:
+ return sAggregationExceptionsProjectionMap.getColumnNames();
+
+ case SETTINGS:
+ return sSettingsProjectionMap.getColumnNames();
+
+ case DIRECTORIES:
+ case DIRECTORIES_ID:
+ return sDirectoryProjectionMap.getColumnNames();
+
+ default:
+ return null;
+ }
+ }
+
private void setDisplayName(long rawContactId, int displayNameSource,
String displayNamePrimary, String displayNameAlternative, String phoneticName,
int phoneticNameStyle, String sortKeyPrimary, String sortKeyAlternative) {
@@ -5861,6 +6275,54 @@
}
/**
+ * Takes components of a name from the query parameters and returns a cursor with those
+ * components as well as all missing components. There is no database activity involved
+ * in this so the call can be made on the UI thread.
+ */
+ private Cursor completeName(Uri uri, String[] projection) {
+ if (projection == null) {
+ projection = sDataProjectionMap.getColumnNames();
+ }
+
+ ContentValues values = new ContentValues();
+ StructuredNameRowHandler handler =
+ (StructuredNameRowHandler) getDataRowHandler(StructuredName.CONTENT_ITEM_TYPE);
+
+ copyQueryParamsToContentValues(values, uri,
+ StructuredName.DISPLAY_NAME,
+ StructuredName.PREFIX,
+ StructuredName.GIVEN_NAME,
+ StructuredName.MIDDLE_NAME,
+ StructuredName.FAMILY_NAME,
+ StructuredName.SUFFIX,
+ StructuredName.PHONETIC_NAME,
+ StructuredName.PHONETIC_FAMILY_NAME,
+ StructuredName.PHONETIC_MIDDLE_NAME,
+ StructuredName.PHONETIC_GIVEN_NAME
+ );
+
+ handler.fixStructuredNameComponents(values, values);
+
+ MatrixCursor cursor = new MatrixCursor(projection);
+ Object[] row = new Object[projection.length];
+ for (int i = 0; i < projection.length; i++) {
+ row[i] = values.get(projection[i]);
+ }
+ cursor.addRow(row);
+ return cursor;
+ }
+
+ private void copyQueryParamsToContentValues(ContentValues values, Uri uri, String... columns) {
+ for (String column : columns) {
+ String param = uri.getQueryParameter(column);
+ if (param != null) {
+ values.put(column, param);
+ }
+ }
+ }
+
+
+ /**
* Inserts an argument at the beginning of the selection arg list.
*/
private String[] insertSelectionArg(String[] selectionArgs, String arg) {
@@ -5934,6 +6396,7 @@
return writable;
}
+
/* package */ static boolean readBooleanQueryParameter(Uri uri, String parameter,
boolean defaultValue) {
diff --git a/src/com/android/providers/contacts/CountryMonitor.java b/src/com/android/providers/contacts/CountryMonitor.java
new file mode 100644
index 0000000..c370b38
--- /dev/null
+++ b/src/com/android/providers/contacts/CountryMonitor.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License
+ */
+
+package com.android.providers.contacts;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryDetector;
+import android.location.CountryListener;
+import android.os.Looper;
+
+/**
+ * This class monitors the change of country.
+ * <p>
+ * {@link #getCountryIso()} is used to get the ISO 3166-1 two letters country
+ * code of current country.
+ */
+public class CountryMonitor {
+ private static CountryMonitor sSingleton;
+ private String mCurrentCountryIso;
+ private Context mContext;
+
+ public synchronized static CountryMonitor getInstance(Context context) {
+ if (sSingleton == null) {
+ sSingleton = new CountryMonitor(context);
+ }
+ return sSingleton;
+ }
+
+ private CountryMonitor(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Get the current country code
+ *
+ * @return the ISO 3166-1 two letters country code of current country.
+ */
+ public synchronized String getCountryIso() {
+ if (mCurrentCountryIso == null) {
+ final CountryDetector countryDetector =
+ (CountryDetector) mContext.getSystemService(Context.COUNTRY_DETECTOR);
+ mCurrentCountryIso = countryDetector.detectCountry().getCountryIso();
+ countryDetector.addCountryListener(new CountryListener() {
+ public void onCountryDetected(Country country) {
+ synchronized (sSingleton) {
+ mCurrentCountryIso = country.getCountryIso();
+ }
+ }
+ }, Looper.getMainLooper());
+ }
+ return mCurrentCountryIso;
+ }
+}
diff --git a/src/com/android/providers/contacts/GlobalSearchSupport.java b/src/com/android/providers/contacts/GlobalSearchSupport.java
index d890310..96bd39c 100644
--- a/src/com/android/providers/contacts/GlobalSearchSupport.java
+++ b/src/com/android/providers/contacts/GlobalSearchSupport.java
@@ -27,6 +27,7 @@
import android.content.ContentUris;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.provider.Contacts.Intents;
@@ -74,7 +75,8 @@
private interface SearchSuggestionQuery {
public static final String TABLE = "data "
+ " JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) "
- + " JOIN contacts ON (raw_contacts.contact_id = contacts._id)"
+ + " JOIN visible_contacts on (raw_contacts.contact_id = visible_contacts._id) "
+ + " JOIN contacts ON (visible_contacts._id = contacts._id) "
+ " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON ("
+ Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")";
@@ -342,18 +344,6 @@
appendMimeTypeFilter(sb);
sb.append(" AND " + DataColumns.CONCRETE_RAW_CONTACT_ID + " IN ");
mContactsProvider.appendRawContactsByFilterAsNestedQuery(sb, searchClause);
-
- /*
- * Prepending "+" to the IN_VISIBLE_GROUP column disables the index on the
- * that column. The logic is this: let's say we have 10,000 contacts
- * of which 500 are visible. The first letter we type narrows this down
- * to 10,000/26 = 384, which is already less than 500 that we would get
- * from the IN_VISIBLE_GROUP index. Typing the second letter will narrow
- * the search down to 10,000/26/26 = 14 contacts. And a lot of people
- * will have more that 5% of their contacts visible, while the alphabet
- * will always have 26 letters.
- */
- sb.append(" AND " + "+" + Contacts.IN_VISIBLE_GROUP + "=1");
String selection = sb.toString();
return buildCursorForSearchSuggestions(db, selection, null, limit);
diff --git a/src/com/android/providers/contacts/LegacyContactImporter.java b/src/com/android/providers/contacts/LegacyContactImporter.java
index 2634d44..ae89cc8 100644
--- a/src/com/android/providers/contacts/LegacyContactImporter.java
+++ b/src/com/android/providers/contacts/LegacyContactImporter.java
@@ -399,9 +399,8 @@
RawContacts.ACCOUNT_NAME + "," +
RawContacts.ACCOUNT_TYPE + "," +
RawContacts.SOURCE_ID + "," +
- RawContactsColumns.DISPLAY_NAME + "," +
- RawContactsColumns.CONTACT_IN_VISIBLE_GROUP +
- ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
+ RawContactsColumns.DISPLAY_NAME +
+ ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
int ID = 1;
int CONTACT_ID = 2;
@@ -417,7 +416,6 @@
int ACCOUNT_TYPE = 12;
int SOURCE_ID = 13;
int DISPLAY_NAME = 14;
- int CONTACT_IN_VISIBLE_GROUP = 15;
}
private interface ContactsInsert {
@@ -428,9 +426,8 @@
Contacts.SEND_TO_VOICEMAIL + "," +
Contacts.STARRED + "," +
Contacts.TIMES_CONTACTED + "," +
- Contacts.NAME_RAW_CONTACT_ID + "," +
- Contacts.IN_VISIBLE_GROUP +
- ") VALUES (?,?,?,?,?,?,?,?)";
+ Contacts.NAME_RAW_CONTACT_ID +
+ ") VALUES (?,?,?,?,?,?,?)";
int ID = 1;
int CUSTOM_RINGTONE = 2;
@@ -439,7 +436,6 @@
int STARRED = 5;
int TIMES_CONTACTED = 6;
int NAME_RAW_CONTACT_ID = 7;
- int IN_VISIBLE_GROUP = 8;
}
private interface StructuredNameInsert {
@@ -555,7 +551,6 @@
c.getString(PeopleQuery._SYNC_LOCAL_ID));
bindString(insert, RawContactsInsert.DISPLAY_NAME,
c.getString(PeopleQuery.NAME));
- insert.bindLong(RawContactsInsert.CONTACT_IN_VISIBLE_GROUP, 1);
String account = c.getString(PeopleQuery._SYNC_ACCOUNT);
if (!TextUtils.isEmpty(account)) {
@@ -585,7 +580,6 @@
insert.bindLong(ContactsInsert.TIMES_CONTACTED,
c.getLong(PeopleQuery.TIMES_CONTACTED));
insert.bindLong(ContactsInsert.NAME_RAW_CONTACT_ID, id);
- insert.bindLong(ContactsInsert.IN_VISIBLE_GROUP, 1);
insert(insert);
}
diff --git a/src/com/android/providers/contacts/LocaleChangeReceiver.java b/src/com/android/providers/contacts/LocaleChangeReceiver.java
new file mode 100644
index 0000000..290b373
--- /dev/null
+++ b/src/com/android/providers/contacts/LocaleChangeReceiver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.provider.ContactsContract;
+
+/**
+ * Locale change intent receiver that invokes {@link ContactsProvider2#onLocaleChanged} to update
+ * the database for the new locale.
+ */
+public class LocaleChangeReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ IContentProvider iprovider =
+ context.getContentResolver().acquireProvider(ContactsContract.AUTHORITY);
+ ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
+ if (provider instanceof ContactsProvider2) {
+ ((ContactsProvider2)provider).onLocaleChanged();
+ }
+ }
+}
diff --git a/src/com/android/providers/contacts/NameLookupBuilder.java b/src/com/android/providers/contacts/NameLookupBuilder.java
index 00416c9..46ce481 100644
--- a/src/com/android/providers/contacts/NameLookupBuilder.java
+++ b/src/com/android/providers/contacts/NameLookupBuilder.java
@@ -36,7 +36,7 @@
private StringBuilder mStringBuilder = new StringBuilder();
private String[] mNames = new String[NameSplitter.MAX_TOKENS];
- private static int[] KOREAN_JAUM_CONVERT_MAP = {
+ private static final int[] KOREAN_JAUM_CONVERT_MAP = {
// JAUM in Hangul Compatibility Jamo area 0x3131 ~ 0x314E to
// in Hangul Jamo area 0x1100 ~ 0x1112
0x1100, // 0x3131 HANGUL LETTER KIYEOK
@@ -70,8 +70,6 @@
0x1111, // 0x314D HANGUL LETTER PHIEUPH
0x1112 // 0x314E HANGUL LETTER HIEUH
};
- private static int KOREAN_JAUM_CONVERT_MAP_COUNT = 30;
-
public NameLookupBuilder(NameSplitter splitter) {
mSplitter = splitter;
@@ -139,13 +137,23 @@
insertNameVariants(rawContactId, dataId, 0, tokenCount, !tooManyTokens, true);
insertNicknamePermutations(rawContactId, dataId, 0, tokenCount);
insertNameShorthandLookup(rawContactId, dataId, name, fullNameStyle);
- insertLocaleBasedSpecificLookup(rawContactId, dataId, name, fullNameStyle);
+ insertNameLookupForLocaleBasedName(rawContactId, dataId, name, fullNameStyle);
}
- private void insertLocaleBasedSpecificLookup(long rawContactId, long dataId, String name,
- int fullNameStyle) {
+ /**
+ * Insert more name indexes according to locale specifies.
+ */
+ private void insertNameLookupForLocaleBasedName(long rawContactId, long dataId,
+ String fullName, int fullNameStyle) {
if (fullNameStyle == FullNameStyle.KOREAN) {
- insertKoreanNameConsonantsLookup(rawContactId, dataId, name);
+ NameSplitter.Name name = new NameSplitter.Name();
+ mSplitter.split(name, fullName, fullNameStyle);
+ if (name.givenNames != null) {
+ insertNameLookup(rawContactId, dataId, NameLookupType.NAME_SHORTHAND,
+ normalizeName(name.givenNames));
+ insertKoreanNameConsonantsLookup(rawContactId, dataId, name.givenNames);
+ }
+ insertKoreanNameConsonantsLookup(rawContactId, dataId, fullName);
}
}
@@ -161,8 +169,8 @@
mStringBuilder.setLength(0);
do {
character = name.codePointAt(position++);
- if (character == 0x20) {
- // Skip spaces.
+ if ((character == 0x20) || (character == 0x2c) || (character == 0x2E)) {
+ // Skip spaces, commas and periods.
continue;
}
// Exclude characters that are not in Korean leading consonants area
@@ -181,7 +189,7 @@
} else if (character >= 0x3131) {
// Hangul Compatibility Jamo area 0x3131 ~ 0x314E :
// Convert to Hangul Jamo area 0x1100 ~ 0x1112
- if (character - 0x3131 >= KOREAN_JAUM_CONVERT_MAP_COUNT) {
+ if (character - 0x3131 >= KOREAN_JAUM_CONVERT_MAP.length) {
// This is not lead-consonant
break;
}
diff --git a/src/com/android/providers/contacts/NameNormalizer.java b/src/com/android/providers/contacts/NameNormalizer.java
index 6dfe8bd..2ac3865 100644
--- a/src/com/android/providers/contacts/NameNormalizer.java
+++ b/src/com/android/providers/contacts/NameNormalizer.java
@@ -15,11 +15,10 @@
*/
package com.android.providers.contacts;
-import com.ibm.icu4jni.text.CollationAttribute;
-import com.ibm.icu4jni.text.CollationKey; // TODO: java.text.CollationKey post-froyo
-import com.ibm.icu4jni.text.Collator;
-import com.ibm.icu4jni.text.RuleBasedCollator;
import java.util.Locale;
+import java.text.Collator;
+import java.text.CollationKey;
+import java.text.RuleBasedCollator;
/**
* Converts a name to a normalized form by removing all non-letter characters and normalizing
@@ -37,14 +36,12 @@
private static final RuleBasedCollator sComplexityCollator;
static {
sComplexityCollator = (RuleBasedCollator)Collator.getInstance(Locale.getDefault());
- sComplexityCollator.setStrength(Collator.TERTIARY);
- sComplexityCollator.setAttribute(CollationAttribute.CASE_FIRST,
- CollationAttribute.VALUE_LOWER_FIRST);
+ sComplexityCollator.setStrength(Collator.SECONDARY);
}
/**
* Converts the supplied name to a string that can be used to perform approximate matching
- * of names. It ignores non-letter characters and removes accents.
+ * of names. It ignores non-letter, non-digit characters, and removes accents.
*/
public static String normalize(String name) {
CollationKey key = sCompressingCollator.getCollationKey(lettersAndDigitsOnly(name));
@@ -56,17 +53,24 @@
* of mixed case characters, accents and, if all else is equal, length.
*/
public static int compareComplexity(String name1, String name2) {
- int diff = sComplexityCollator.compare(lettersAndDigitsOnly(name1),
- lettersAndDigitsOnly(name2));
+ String clean1 = lettersAndDigitsOnly(name1);
+ String clean2 = lettersAndDigitsOnly(name2);
+ int diff = sComplexityCollator.compare(clean1, clean2);
if (diff != 0) {
return diff;
}
-
+ // compareTo sorts uppercase first. We know that there are no non-case
+ // differences from the above test, so we can negate here to get the
+ // lowercase-first comparison we really want...
+ diff = -clean1.compareTo(clean2);
+ if (diff != 0) {
+ return diff;
+ }
return name1.length() - name2.length();
}
/**
- * Returns a string containing just the letters from the original string.
+ * Returns a string containing just the letters and digits from the original string.
*/
private static String lettersAndDigitsOnly(String name) {
char[] letters = name.toCharArray();
diff --git a/src/com/android/providers/contacts/NameSplitter.java b/src/com/android/providers/contacts/NameSplitter.java
index 3f9bc86..642bb8f 100644
--- a/src/com/android/providers/contacts/NameSplitter.java
+++ b/src/com/android/providers/contacts/NameSplitter.java
@@ -15,9 +15,6 @@
*/
package com.android.providers.contacts;
-import com.android.internal.util.HanziToPinyin;
-import com.android.internal.util.HanziToPinyin.Token;
-
import android.content.ContentValues;
import android.provider.ContactsContract.FullNameStyle;
import android.provider.ContactsContract.PhoneticNameStyle;
@@ -25,7 +22,6 @@
import android.text.TextUtils;
import java.lang.Character.UnicodeBlock;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.StringTokenizer;
@@ -63,6 +59,26 @@
private final Locale mLocale;
private final String mLanguage;
+ /**
+ * Two-Chracter long Korean family names.
+ * http://ko.wikipedia.org/wiki/%ED%95%9C%EA%B5%AD%EC%9D%98_%EB%B3%B5%EC%84%B1
+ */
+ private static final String[] KOREAN_TWO_CHARCTER_FAMILY_NAMES = {
+ "\uAC15\uC804", // Gang Jeon
+ "\uB0A8\uAD81", // Nam Goong
+ "\uB3C5\uACE0", // Dok Go
+ "\uB3D9\uBC29", // Dong Bang
+ "\uB9DD\uC808", // Mang Jeol
+ "\uC0AC\uACF5", // Sa Gong
+ "\uC11C\uBB38", // Seo Moon
+ "\uC120\uC6B0", // Seon Woo
+ "\uC18C\uBD09", // So Bong
+ "\uC5B4\uAE08", // Uh Geum
+ "\uC7A5\uACE1", // Jang Gok
+ "\uC81C\uAC08", // Je Gal
+ "\uD669\uBCF4" // Hwang Bo
+ };
+
public static class Name {
public String prefix;
public String givenNames;
@@ -335,6 +351,18 @@
fullNameStyle = getAdjustedFullNameStyle(fullNameStyle);
}
+ split(name, fullName, fullNameStyle);
+ }
+
+ /**
+ * Parses a full name and returns parsed components in the Name object
+ * with a given fullNameStyle.
+ */
+ public void split(Name name, String fullName, int fullNameStyle) {
+ if (fullName == null) {
+ return;
+ }
+
name.fullNameStyle = fullNameStyle;
switch (fullNameStyle) {
@@ -343,8 +371,11 @@
break;
case FullNameStyle.JAPANESE:
+ splitJapaneseName(name, fullName);
+ break;
+
case FullNameStyle.KOREAN:
- splitJapaneseOrKoreanName(name, fullName);
+ splitKoreanName(name, fullName);
break;
default:
@@ -427,7 +458,7 @@
* [family name] given name(s)
* </pre>
*/
- private void splitJapaneseOrKoreanName(Name name, String fullName) {
+ private void splitJapaneseName(Name name, String fullName) {
StringTokenizer tokenizer = new StringTokenizer(fullName);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
@@ -443,6 +474,47 @@
}
/**
+ * Splits a full name composed according to the Korean tradition:
+ * <pre>
+ * [family name] given name(s)
+ * </pre>
+ */
+ private void splitKoreanName(Name name, String fullName) {
+ StringTokenizer tokenizer = new StringTokenizer(fullName);
+ if (tokenizer.countTokens() > 1) {
+ // Each name can be identified by separators.
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (name.givenNames == null) {
+ name.givenNames = token;
+ } else if (name.familyName == null) {
+ name.familyName = name.givenNames;
+ name.givenNames = token;
+ } else {
+ name.givenNames += " " + token;
+ }
+ }
+ } else {
+ // There is no separator. Try to guess family name.
+ // The length of most family names is 1.
+ int familyNameLength = 1;
+
+ // Compare with 2-length family names.
+ for (String twoLengthFamilyName : KOREAN_TWO_CHARCTER_FAMILY_NAMES) {
+ if (fullName.startsWith(twoLengthFamilyName)) {
+ familyNameLength = 2;
+ break;
+ }
+ }
+
+ name.familyName = fullName.substring(0, familyNameLength);
+ if (fullName.length() > familyNameLength) {
+ name.givenNames = fullName.substring(familyNameLength);
+ }
+ }
+ }
+
+ /**
* Concatenates components of a name according to the rules dictated by the name style.
*
* @param givenNameFirst is ignored for CJK display name styles
diff --git a/src/com/android/providers/contacts/PackageIntentReceiver.java b/src/com/android/providers/contacts/PackageIntentReceiver.java
new file mode 100644
index 0000000..b65f469
--- /dev/null
+++ b/src/com/android/providers/contacts/PackageIntentReceiver.java
@@ -0,0 +1,44 @@
+/*
+ * 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
+ */
+
+package com.android.providers.contacts;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.ContactsContract;
+
+/**
+ * Package intent receiver that invokes {@link ContactsProvider2#onPackageChanged} to update
+ * the contact directory list.
+ */
+public class PackageIntentReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Uri packageUri = intent.getData();
+ String packageName = packageUri.getSchemeSpecificPart();
+ IContentProvider iprovider =
+ context.getContentResolver().acquireProvider(ContactsContract.AUTHORITY);
+ ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
+ if (provider instanceof ContactsProvider2) {
+ ((ContactsProvider2)provider).onPackageChanged(packageName);
+ }
+ }
+}
diff --git a/src/com/android/providers/contacts/ProjectionMap.java b/src/com/android/providers/contacts/ProjectionMap.java
new file mode 100644
index 0000000..56198b8
--- /dev/null
+++ b/src/com/android/providers/contacts/ProjectionMap.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A convenience wrapper for a projection map. Makes it easier to create and use projection maps.
+ */
+public class ProjectionMap extends HashMap<String, String> {
+
+ public static class Builder {
+
+ private ProjectionMap mMap = new ProjectionMap();
+
+ public Builder add(String column) {
+ mMap.putColumn(column, column);
+ return this;
+ }
+
+ public Builder add(String alias, String expression) {
+ mMap.putColumn(alias, expression + " AS " + alias);
+ return this;
+ }
+
+ public Builder addAll(ProjectionMap map) {
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ mMap.putColumn(entry.getKey(), entry.getValue());
+ }
+ return this;
+ }
+
+ public ProjectionMap build() {
+ String[] columns = new String[mMap.size()];
+ mMap.keySet().toArray(columns);
+ Arrays.sort(columns);
+ mMap.mColumns = columns;
+ return mMap;
+ }
+
+ }
+
+ public String[] mColumns;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns a sorted array of all column names in the projection map.
+ */
+ public String[] getColumnNames() {
+ return mColumns;
+ }
+
+ private void putColumn(String alias, String column) {
+ super.put(alias, column);
+ }
+
+ @Override
+ public String put(String key, String value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putAll(Map<? extends String, ? extends String> map) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/com/android/providers/contacts/ReorderingCursorWrapper.java b/src/com/android/providers/contacts/ReorderingCursorWrapper.java
index d332fa3..e52b095 100644
--- a/src/com/android/providers/contacts/ReorderingCursorWrapper.java
+++ b/src/com/android/providers/contacts/ReorderingCursorWrapper.java
@@ -94,6 +94,11 @@
}
@Override
+ public int getType(int column) {
+ return mCursor.getType(column);
+ }
+
+ @Override
public boolean isNull(int column) {
return mCursor.isNull(column);
}
diff --git a/tests/assets/test1/expected_contacts.txt b/tests/assets/test1/expected_contacts.txt
index ebe4dad..a73f0fe 100644
--- a/tests/assets/test1/expected_contacts.txt
+++ b/tests/assets/test1/expected_contacts.txt
@@ -10,7 +10,7 @@
10 starred=0
11 in_visible_group=0
12 has_phone_number=0
-13 lookup=2273r1-523450522C46324E483C32
+13 lookup=2273r1-533551532D47334F493D33
14 }
15 1 {
16 _id=2
@@ -52,5 +52,5 @@
52 starred=0
53 in_visible_group=1
54 has_phone_number=1
-55 lookup=2187r11-2C3232343248462C46324E483C32
+55 lookup=2187r11-2D3333353349472D47334F493D33
56 }
diff --git a/tests/assets/testSynced/expected_contacts.txt b/tests/assets/testSynced/expected_contacts.txt
index 8ce6f03..bcb5cf8 100644
--- a/tests/assets/testSynced/expected_contacts.txt
+++ b/tests/assets/testSynced/expected_contacts.txt
@@ -10,7 +10,7 @@
10 starred=0
11 in_visible_group=0
12 has_phone_number=0
-13 lookup=2273r1-423444445C52345052
+13 lookup=2273r1-433545455D53355153
14 }
15 1 {
16 _id=2
@@ -94,7 +94,7 @@
94 starred=0
95 in_visible_group=1
96 has_phone_number=1
-97 lookup=389r7-464852505C463034324654442E344E
+97 lookup=389r7-474953515D473135334755452F354F
98 }
99 7 {
100 _id=8
@@ -108,7 +108,7 @@
108 starred=0
109 in_visible_group=1
110 has_phone_number=1
-111 lookup=389r8-464852505C463034324654442E344E442C3C42
+111 lookup=389r8-474953515D473135334755452F354F452D3D43
112 }
113 8 {
114 _id=9
@@ -122,7 +122,7 @@
122 starred=0
123 in_visible_group=1
124 has_phone_number=0
-125 lookup=389r9-464852505C46303432442C3C42
+125 lookup=389r9-474953515D47313533452D3D43
126 }
127 9 {
128 _id=10
@@ -136,7 +136,7 @@
136 starred=0
137 in_visible_group=1
138 has_phone_number=0
-139 lookup=389r10-464852505C46303432
+139 lookup=389r10-474953515D47313533
140 }
141 10 {
142 _id=11
@@ -150,7 +150,7 @@
150 starred=0
151 in_visible_group=1
152 has_phone_number=1
-153 lookup=389r11-297E297E297E297E297E297E297E297E297E297E
+153 lookup=389r11-2A902A902A902A902A902A902A902A902A902A90
154 }
155 11 {
156 _id=12
@@ -164,5 +164,5 @@
164 starred=0
165 in_visible_group=1
166 has_phone_number=0
-167 lookup=389r12-464852505C4630343238442C3C42304844
+167 lookup=389r12-474953515D4731353339452D3D43314945
168 }
\ No newline at end of file
diff --git a/tests/assets/testUnsynced/expected_contacts.txt b/tests/assets/testUnsynced/expected_contacts.txt
index 32dfdfb..e6f95ba 100644
--- a/tests/assets/testUnsynced/expected_contacts.txt
+++ b/tests/assets/testUnsynced/expected_contacts.txt
@@ -10,7 +10,7 @@
10 starred=0
11 in_visible_group=1
12 has_phone_number=1
-13 lookup=0r1-4654442E344E2C4632442C3C42
+13 lookup=0r1-4755452F354F2D4733452D3D43
14 }
15 1 {
16 _id=2
@@ -24,7 +24,7 @@
24 starred=0
25 in_visible_group=1
26 has_phone_number=1
-27 lookup=0r2-4654442E344E
+27 lookup=0r2-4755452F354F
28 }
29 2 {
30 _id=3
@@ -38,7 +38,7 @@
38 starred=0
39 in_visible_group=1
40 has_phone_number=0
-41 lookup=0r3-442C3C42
+41 lookup=0r3-452D3D43
42 }
43 3 {
44 _id=4
@@ -52,7 +52,7 @@
52 starred=0
53 in_visible_group=1
54 has_phone_number=1
-55 lookup=0r4-2988298E298C297E2984298A297C2980
+55 lookup=0r4-2A9A2AA02A9E2A902A962A9C2A8E2A92
56 }
57 4 {
58 _id=5
@@ -66,5 +66,5 @@
66 starred=0
67 in_visible_group=1
68 has_phone_number=0
-69 lookup=0r5-442C3C42324844304844
+69 lookup=0r5-452D3D43334945314945
70 }
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index ebca24d..d96c24e 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -18,6 +18,8 @@
import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
+import com.google.android.collect.Sets;
+
import android.accounts.Account;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -30,12 +32,6 @@
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.Settings;
-import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -45,7 +41,14 @@
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.StatusUpdates;
import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
import android.test.mock.MockContentResolver;
import android.util.Log;
@@ -57,8 +60,8 @@
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
/**
* A common superclass for {@link ContactsProvider2}-related tests.
@@ -171,24 +174,46 @@
protected long createRawContact(Account account, String... extras) {
ContentValues values = new ContentValues();
- for (int i = 0; i < extras.length; ) {
- values.put(extras[i], extras[i + 1]);
- i += 2;
- }
+ extrasVarArgsToValues(values, extras);
final Uri uri = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account);
Uri contactUri = mResolver.insert(uri, values);
return ContentUris.parseId(contactUri);
}
+ protected int updateItem(Uri uri, long id, String... extras) {
+ Uri itemUri = ContentUris.withAppendedId(uri, id);
+ return updateItem(itemUri, extras);
+ }
+
+ protected int updateItem(Uri uri, String... extras) {
+ ContentValues values = new ContentValues();
+ extrasVarArgsToValues(values, extras);
+ return mResolver.update(uri, values, null, null);
+ }
+
+ private static void extrasVarArgsToValues(ContentValues values, String... extras) {
+ for (int i = 0; i < extras.length; ) {
+ values.put(extras[i], extras[i + 1]);
+ i += 2;
+ }
+ }
+
protected long createGroup(Account account, String sourceId, String title) {
- return createGroup(account, sourceId, title, 1);
+ return createGroup(account, sourceId, title, 1, false, false);
}
protected long createGroup(Account account, String sourceId, String title, int visible) {
+ return createGroup(account, sourceId, title, visible, false, false);
+ }
+
+ protected long createGroup(Account account, String sourceId, String title,
+ int visible, boolean autoAdd, boolean favorite) {
ContentValues values = new ContentValues();
values.put(Groups.SOURCE_ID, sourceId);
values.put(Groups.TITLE, title);
values.put(Groups.GROUP_VISIBLE, visible);
+ values.put(Groups.AUTO_ADD, autoAdd ? 1 : 0);
+ values.put(Groups.FAVORITES, favorite ? 1 : 0);
final Uri uri = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account);
return ContentUris.parseId(mResolver.insert(uri, values));
}
@@ -420,6 +445,16 @@
return photoId;
}
+ protected boolean queryRawContactIsStarred(long rawContactId) {
+ Cursor c = queryRawContact(rawContactId);
+ try {
+ assertTrue(c.moveToFirst());
+ return c.getLong(c.getColumnIndex(RawContacts.STARRED)) != 0;
+ } finally {
+ c.close();
+ }
+ }
+
protected String queryDisplayName(long contactId) {
Cursor c = queryContact(contactId);
assertTrue(c.moveToFirst());
@@ -428,7 +463,7 @@
return displayName;
}
- private String queryLookupKey(long contactId) {
+ protected String queryLookupKey(long contactId) {
Cursor c = queryContact(contactId);
assertTrue(c.moveToFirst());
String lookupKey = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY));
@@ -578,6 +613,14 @@
}
}
+ protected void assertNoRowsAndClose(Cursor c) {
+ try {
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+ }
+
protected static class IdComparator implements Comparator<ContentValues> {
public int compare(ContentValues o1, ContentValues o2) {
long id1 = o1.getAsLong(ContactsContract.Data._ID);
@@ -689,6 +732,10 @@
assertStoredValues(rowUri, null, null, expectedValues);
}
+ protected void assertStoredValues(Uri rowUri, ContentValues[] expectedValues) {
+ assertStoredValues(rowUri, null, null, expectedValues);
+ }
+
protected void assertStoredValues(Uri rowUri, String selection, String[] selectionArgs,
ContentValues expectedValues) {
Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null);
@@ -712,6 +759,17 @@
}
}
+ protected void assertStoredValues(
+ Uri rowUri, String selection, String[] selectionArgs, ContentValues[] expectedValues) {
+ Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null);
+ try {
+ assertEquals("Record count", expectedValues.length, c.getCount());
+ assertCursorValues(c, expectedValues);
+ } finally {
+ c.close();
+ }
+ }
+
/**
* Constructs a selection (where clause) out of all supplied values, uses it
* to query the provider and verifies that a single row is returned and it
@@ -760,12 +818,43 @@
}
}
+ protected void assertCursorValue(Cursor cursor, String column, Object expectedValue) {
+ String actualValue = cursor.getString(cursor.getColumnIndex(column));
+ assertEquals("Column " + column, String.valueOf(expectedValue),
+ String.valueOf(actualValue));
+ }
+
protected void assertCursorValues(Cursor cursor, ContentValues expectedValues) {
+ StringBuilder message = new StringBuilder();
+ boolean result = equalsWithExpectedValues(cursor, expectedValues, message);
+ assertTrue(message.toString(), result);
+ }
+
+ protected void assertCursorValues(Cursor cursor, ContentValues[] expectedValues) {
+ StringBuilder message = new StringBuilder();
+ for (ContentValues v : expectedValues) {
+ boolean found = false;
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ found = equalsWithExpectedValues(cursor, v, message);
+ if (found) {
+ break;
+ }
+ }
+ assertTrue("Expected values can not be found " + v + message.toString(), found);
+ }
+ }
+
+ private boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
+ StringBuilder msgBuffer) {
Set<Map.Entry<String, Object>> entries = expectedValues.valueSet();
for (Map.Entry<String, Object> entry : entries) {
String column = entry.getKey();
int index = cursor.getColumnIndex(column);
- assertTrue("No such column: " + column, index != -1);
+ if (index == -1) {
+ msgBuffer.append("No such column: ").append(column);
+ return false;
+ }
Object expectedValue = expectedValues.get(column);
String value;
if (expectedValue instanceof byte[]) {
@@ -775,8 +864,20 @@
expectedValue = expectedValues.getAsString(column);
value = cursor.getString(index);
}
- assertEquals("Column value " + column, expectedValue, value);
+ if (expectedValue != null && !expectedValue.equals(value) || value != null
+ && !value.equals(expectedValue)) {
+ msgBuffer
+ .append("Column value ")
+ .append(column)
+ .append(" expected <")
+ .append(expectedValue)
+ .append(">, but was <")
+ .append(value)
+ .append('>');
+ return false;
+ }
}
+ return true;
}
private String[] buildProjection(ContentValues values) {
@@ -880,6 +981,14 @@
assertEquals(expected, ((SynchronousContactsProvider2)mActor.provider).isNetworkNotified());
}
+ protected void assertProjection(Uri uri, String[] expectedProjection) {
+ Cursor cursor = mResolver.query(uri, null, "0", null, null);
+ String[] actualProjection = cursor.getColumnNames();
+ MoreAsserts.assertEquals("Incorrect projection for URI: " + uri,
+ Sets.newHashSet(expectedProjection), Sets.newHashSet(actualProjection));
+ cursor.close();
+ }
+
/**
* A contact in the database, and the attributes used to create it. Construct using
* {@link GoldenContactBuilder#build()}.
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index af5a1fe..392fa65 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -63,6 +63,7 @@
ContentValues values = new ContentValues();
putCallValues(values);
Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
+ values.put(Calls.COUNTRY_ISO, "us");
assertStoredValues(uri, values);
assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
}
@@ -137,6 +138,7 @@
values.put(Calls.CACHED_NAME, "1-800-GOOG-411");
values.put(Calls.CACHED_NUMBER_TYPE, Phone.TYPE_CUSTOM);
values.put(Calls.CACHED_NUMBER_LABEL, "Directory");
+ values.put(Calls.COUNTRY_ISO, "us");
assertStoredValues(uri, values);
}
@@ -158,6 +160,11 @@
}
return mDbHelper;
}
+
+ @Override
+ protected String getCurrentCountryIso() {
+ return "us";
+ }
}
}
diff --git a/tests/src/com/android/providers/contacts/ContactAggregatorPerformanceTest.java b/tests/src/com/android/providers/contacts/ContactAggregatorPerformanceTest.java
index 4b8429e..013a706 100644
--- a/tests/src/com/android/providers/contacts/ContactAggregatorPerformanceTest.java
+++ b/tests/src/com/android/providers/contacts/ContactAggregatorPerformanceTest.java
@@ -17,6 +17,7 @@
package com.android.providers.contacts;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.Debug;
import android.provider.ContactsContract;
@@ -71,6 +72,13 @@
public String getPackageName() {
return "no.package";
}
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = "contactsTestPackage";
+ return ai;
+ }
};
RenamingDelegatingContext targetContextWrapper =
new RenamingDelegatingContext(context, targetContext, "perf.");
diff --git a/tests/src/com/android/providers/contacts/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/ContactAggregatorTest.java
index 328b03c..551550a 100644
--- a/tests/src/com/android/providers/contacts/ContactAggregatorTest.java
+++ b/tests/src/com/android/providers/contacts/ContactAggregatorTest.java
@@ -28,6 +28,7 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.AggregationExceptions;
import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Organization;
@@ -967,6 +968,54 @@
assertAggregated(rawContactId1, rawContactId2);
}
+ public void testAggregationSuggestionsQueryBuilderWithContactId() throws Exception {
+ Uri uri = AggregationSuggestions.builder().setContactId(12).setLimit(7).build();
+ assertEquals("content://com.android.contacts/contacts/12/suggestions?limit=7",
+ uri.toString());
+ }
+
+ public void testAggregationSuggestionsQueryBuilderWithValues() throws Exception {
+ Uri uri = AggregationSuggestions.builder()
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_NAME, "name1")
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_NAME, "name2")
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_EMAIL, "email1")
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_EMAIL, "email2")
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_PHONE, "phone1")
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_NICKNAME, "nickname1")
+ .setLimit(7)
+ .build();
+ assertEquals("content://com.android.contacts/contacts/0/suggestions?"
+ + "limit=7"
+ + "&query=name%3Aname1"
+ + "&query=name%3Aname2"
+ + "&query=email%3Aemail1"
+ + "&query=email%3Aemail2"
+ + "&query=phone%3Aphone1"
+ + "&query=nickname%3Anickname1", uri.toString());
+ }
+
+ public void testAggregationSuggestionsByName() throws Exception {
+ long rawContactId1 = createRawContactWithName("first1", "last1");
+ long rawContactId2 = createRawContactWithName("first2", "last2");
+
+ Uri uri = AggregationSuggestions.builder()
+ .addParameter(AggregationSuggestions.PARAMETER_MATCH_NAME, "last1 first1")
+ .build();
+
+ Cursor cursor = mResolver.query(
+ uri, new String[] { Contacts._ID, Contacts.DISPLAY_NAME }, null, null, null);
+
+ assertEquals(1, cursor.getCount());
+
+ cursor.moveToFirst();
+
+ ContentValues values = new ContentValues();
+ values.put(Contacts._ID, queryContactId(rawContactId1));
+ values.put(Contacts.DISPLAY_NAME, "first1 last1");
+ assertCursorValues(cursor, values);
+ cursor.close();
+ }
+
private void assertSuggestions(long contactId, long... suggestions) {
final Uri aggregateUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
Uri uri = Uri.withAppendedPath(aggregateUri,
diff --git a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
new file mode 100644
index 0000000..a4757ca
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.contacts;
+
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
+import com.google.android.collect.Lists;
+
+import android.accounts.Account;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentProvider;
+import android.test.suitebuilder.annotation.LargeTest;
+
+/**
+ * Unit tests for {@link ContactDirectoryManager}. Run the test like this:
+ * <code>
+ * adb shell am instrument -e class com.android.providers.contacts.ContactDirectoryManagerTest
+ * -w com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
+ * </code>
+ */
+@LargeTest
+public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
+
+ private ContactsMockPackageManager mPackageManager;
+
+ private ContactsProvider2 mProvider;
+
+ private ContactDirectoryManager mDirectoryManager;
+
+ public static class MockContactDirectoryProvider extends MockContentProvider {
+
+ private String mAuthority;
+
+ private MatrixCursor mResponse;
+
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ mAuthority = info.authority;
+ }
+
+ public MatrixCursor createResponseCursor() {
+ mResponse = new MatrixCursor(
+ new String[] { Directory.ACCOUNT_NAME, Directory.ACCOUNT_TYPE,
+ Directory.DISPLAY_NAME, Directory.TYPE_RESOURCE_ID,
+ Directory.EXPORT_SUPPORT, Directory.SHORTCUT_SUPPORT,
+ Directory.PHOTO_SUPPORT });
+
+ return mResponse;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+
+ if (uri.toString().equals("content://" + mAuthority + "/directories")) {
+ return mResponse;
+ } else if (uri.toString().startsWith("content://" + mAuthority + "/contacts")) {
+ MatrixCursor cursor = new MatrixCursor(
+ new String[] { "projection", "selection", "selectionArgs", "sortOrder",
+ "accountName", "accountType"});
+ cursor.addRow(new Object[] {
+ Lists.newArrayList(projection).toString(),
+ selection,
+ Lists.newArrayList(selectionArgs).toString(),
+ sortOrder,
+ uri.getQueryParameter(RawContacts.ACCOUNT_NAME),
+ uri.getQueryParameter(RawContacts.ACCOUNT_TYPE),
+ });
+ return cursor;
+ } else if (uri.toString().startsWith(
+ "content://" + mAuthority + "/aggregation_exceptions")) {
+ return new MatrixCursor(projection);
+ }
+
+ fail("Unexpected uri: " + uri);
+ return null;
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mProvider = (ContactsProvider2) getProvider();
+ mDirectoryManager = mProvider.getContactDirectoryManager();
+
+ mPackageManager = (ContactsMockPackageManager) getProvider()
+ .getContext().getPackageManager();
+ }
+
+ public void testScanAllProviders() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package1", "authority1"),
+ createProviderPackage("test.package2", "authority2")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
+ addDirectoryRow(response1, "account-name2", "account-type2", "display-name2", 2,
+ Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY,
+ Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY);
+
+ MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority2");
+
+ MatrixCursor response2 = provider2.createResponseCursor();
+ addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
+ Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ mDirectoryManager.scanAllPackages();
+
+ Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(5, cursor.getCount());
+
+ cursor.moveToPosition(2);
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
+ "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
+
+ cursor.moveToNext();
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2",
+ "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT,
+ Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY, Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY);
+
+ cursor.moveToNext();
+ assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3",
+ "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY,
+ Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
+
+ cursor.close();
+ }
+
+ public void testPackageInstalled() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(createProviderPackage("test.package1", "authority1")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ mDirectoryManager.scanAllPackages();
+
+ // At this point the manager has discovered a single directory (plus two
+ // standard ones).
+ Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(3, cursor.getCount());
+ cursor.close();
+
+ // Pretend to install another package
+ MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority2");
+
+ MatrixCursor response2 = provider2.createResponseCursor();
+ addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
+ Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ mPackageManager.getInstalledPackages(0).add(
+ createProviderPackage("test.package2", "authority2"));
+
+ mProvider.onPackageChanged("test.package2");
+
+ cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(4, cursor.getCount());
+
+ cursor.moveToPosition(2);
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
+ "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ cursor.moveToNext();
+ assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3",
+ "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY,
+ Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
+
+ cursor.close();
+ }
+
+ public void testPackageUninstalled() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package1", "authority1"),
+ createProviderPackage("test.package2", "authority2")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority2");
+
+ MatrixCursor response2 = provider2.createResponseCursor();
+ addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
+ Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ mDirectoryManager.scanAllPackages();
+
+ // At this point the manager has discovered two custom directories.
+ Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(4, cursor.getCount());
+ cursor.close();
+
+ // Pretend to uninstall one of the packages
+ mPackageManager.getInstalledPackages(0).remove(1);
+
+ mProvider.onPackageChanged("test.package2");
+
+ cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(3, cursor.getCount());
+
+ cursor.moveToPosition(2);
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
+ "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ cursor.close();
+ }
+
+ public void testPackageReplaced() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package1", "authority1"),
+ createProviderPackage("test.package2", "authority2")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority2");
+
+ MatrixCursor response2 = provider2.createResponseCursor();
+ addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
+ Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ mDirectoryManager.scanAllPackages();
+
+ // At this point the manager has discovered two custom directories.
+ Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(4, cursor.getCount());
+ cursor.close();
+
+ // Pretend to replace the package with a different provider inside
+ MatrixCursor response3 = provider2.createResponseCursor();
+ addDirectoryRow(response3, "account-name4", "account-type4", "display-name4", 4,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ mProvider.onPackageChanged("test.package2");
+
+ cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(4, cursor.getCount());
+
+ cursor.moveToPosition(2);
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
+ "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ cursor.moveToNext();
+ assertDirectoryRow(cursor, "test.package2", "authority2", "account-name4", "account-type4",
+ "display-name4", 4, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ cursor.close();
+ }
+
+ public void testAccountRemoval() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package1", "authority1"),
+ createProviderPackage("test.package2", "authority2")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ ((ContactsProvider2)getProvider()).onAccountsUpdated(
+ new Account[]{
+ new Account("account-name1", "account-type1"),
+ new Account("account-name2", "account-type2")});
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+ addDirectoryRow(response1, "account-name2", "account-type2", "display-name2", 2,
+ Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY,
+ Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
+
+ mDirectoryManager.scanAllPackages();
+
+ ((ContactsProvider2)getProvider()).onAccountsUpdated(
+ new Account[]{new Account("account-name1", "account-type1")});
+
+ Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(3, cursor.getCount());
+
+ cursor.moveToPosition(2);
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
+ "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ cursor.close();
+ }
+
+ public void testNotifyDirectoryChange() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(createProviderPackage("test.package1", "authority1")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ mDirectoryManager.scanAllPackages();
+
+ // Pretend to replace the package with a different provider inside
+ MatrixCursor response2 = provider1.createResponseCursor();
+ addDirectoryRow(response2, "account-name2", "account-type2", "display-name2", 2,
+ Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_FULL,
+ Directory.PHOTO_SUPPORT_FULL);
+
+ ContactsContract.Directory.notifyDirectoryChange(mResolver);
+
+ Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+ assertEquals(3, cursor.getCount());
+
+ cursor.moveToPosition(2);
+ assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2",
+ "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT,
+ Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
+
+ cursor.close();
+ }
+
+ public void testForwardingToDirectoryProvider() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(createProviderPackage("test.package1", "authority1")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ mDirectoryManager.scanAllPackages();
+
+ Cursor cursor = mResolver.query(
+ Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null);
+ cursor.moveToPosition(2);
+ long directoryId = cursor.getLong(0);
+ cursor.close();
+
+ Uri contentUri = Contacts.CONTENT_URI.buildUpon().appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
+
+ // The request should be forwarded to TestProvider, which will simply
+ // package arguments and return them to us for verification
+ cursor = mResolver.query(contentUri,
+ new String[]{"f1", "f2"}, "query", new String[]{"s1", "s2"}, "so");
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals("[f1, f2]", cursor.getString(cursor.getColumnIndex("projection")));
+ assertEquals("query", cursor.getString(cursor.getColumnIndex("selection")));
+ assertEquals("[s1, s2]", cursor.getString(cursor.getColumnIndex("selectionArgs")));
+ assertEquals("so", cursor.getString(cursor.getColumnIndex("sortOrder")));
+ assertEquals("account-name1", cursor.getString(cursor.getColumnIndex("accountName")));
+ assertEquals("account-type1", cursor.getString(cursor.getColumnIndex("accountType")));
+ cursor.close();
+ }
+
+ public void testProjectionPopulated() throws Exception {
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(createProviderPackage("test.package1", "authority1")));
+
+ MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
+ MockContactDirectoryProvider.class, "authority1");
+
+ MatrixCursor response1 = provider1.createResponseCursor();
+ addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
+ Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
+ Directory.PHOTO_SUPPORT_NONE);
+
+ mDirectoryManager.scanAllPackages();
+
+ Cursor cursor = mResolver.query(
+ Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null);
+ cursor.moveToPosition(2);
+ long directoryId = cursor.getLong(0);
+ cursor.close();
+
+ Uri contentUri = AggregationExceptions.CONTENT_URI.buildUpon().appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
+
+ // The request should be forwarded to TestProvider, which will return an empty cursor
+ // but the projection should be correctly populated by ContactProvider
+ assertProjection(contentUri, new String[]{
+ AggregationExceptionColumns._ID,
+ AggregationExceptions.TYPE,
+ AggregationExceptions.RAW_CONTACT_ID1,
+ AggregationExceptions.RAW_CONTACT_ID2,
+ });
+ }
+
+ protected PackageInfo createProviderPackage(String packageName, String authority) {
+ PackageInfo providerPackage = new PackageInfo();
+ providerPackage.packageName = packageName;
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.packageName = providerPackage.packageName;
+ providerInfo.authority = authority;
+ providerInfo.metaData = new Bundle();
+ providerInfo.metaData.putBoolean("android.content.ContactDirectory", true);
+ providerPackage.providers = new ProviderInfo[] { providerInfo };
+ return providerPackage;
+ }
+
+ protected void addDirectoryRow(MatrixCursor cursor, String accountName, String accountType,
+ String displayName, int typeResourceId, int exportSupport, int shortcutSupport,
+ int photoSupport) {
+ Object[] row = new Object[cursor.getColumnCount()];
+ row[cursor.getColumnIndex(Directory.ACCOUNT_NAME)] = accountName;
+ row[cursor.getColumnIndex(Directory.ACCOUNT_TYPE)] = accountType;
+ row[cursor.getColumnIndex(Directory.DISPLAY_NAME)] = displayName;
+ row[cursor.getColumnIndex(Directory.TYPE_RESOURCE_ID)] = typeResourceId;
+ row[cursor.getColumnIndex(Directory.EXPORT_SUPPORT)] = exportSupport;
+ row[cursor.getColumnIndex(Directory.SHORTCUT_SUPPORT)] = shortcutSupport;
+ row[cursor.getColumnIndex(Directory.PHOTO_SUPPORT)] = photoSupport;
+ cursor.addRow(row);
+ }
+
+ protected void assertDirectoryRow(Cursor cursor, String packageName, String authority,
+ String accountName, String accountType, String displayName, int typeResourceId,
+ int exportSupport, int shortcutSupport, int photoSupport) {
+ ContentValues values = new ContentValues();
+ values.put(Directory.PACKAGE_NAME, packageName);
+ values.put(Directory.DIRECTORY_AUTHORITY, authority);
+ values.put(Directory.ACCOUNT_NAME, accountName);
+ values.put(Directory.ACCOUNT_TYPE, accountType);
+ values.put(Directory.DISPLAY_NAME, displayName);
+ values.put(Directory.TYPE_RESOURCE_ID, typeResourceId);
+ values.put(Directory.EXPORT_SUPPORT, exportSupport);
+ values.put(Directory.SHORTCUT_SUPPORT, shortcutSupport);
+ values.put(Directory.PHOTO_SUPPORT, photoSupport);
+
+ assertCursorValues(cursor, values);
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java b/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java
index 0a0955e..2e1f7d3 100644
--- a/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java
@@ -95,7 +95,7 @@
assertEquals(allKeys, new HashSet<String>(Arrays.asList(expectedKeys)));
}
- private void testChineseStyleNameWithDifferentLocale() throws Exception {
+ public void testChineseStyleNameWithDifferentLocale() throws Exception {
mContactLocaleUtils.setLocale(Locale.ENGLISH);
assertTrue(mContactLocaleUtils.getSortKey(CHINESE_NAME,
FullNameStyle.CHINESE).equalsIgnoreCase("DU \u675C JUAN \u9D51"));
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 6e0e47a..95b4a7d 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -23,11 +23,12 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Binder;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
@@ -41,11 +42,9 @@
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
import android.test.mock.MockResources;
import android.util.TypedValue;
-import java.util.HashMap;
import java.util.Locale;
/**
@@ -94,7 +93,9 @@
public ContentProvider addProvider(Class<? extends ContentProvider> providerClass,
String authority) throws Exception {
ContentProvider provider = providerClass.newInstance();
- provider.attachInfo(mProviderContext, null);
+ ProviderInfo info = new ProviderInfo();
+ info.authority = authority;
+ provider.attachInfo(mProviderContext, info);
resolver.addProvider(authority, provider);
return provider;
}
@@ -112,7 +113,7 @@
private static class RestrictionMockContext extends MockContext {
private final Context mOverallContext;
private final String mReportedPackageName;
- private final RestrictionMockPackageManager mPackageManager;
+ private final ContactsMockPackageManager mPackageManager;
private final ContentResolver mResolver;
private final Resources mRes;
@@ -125,7 +126,7 @@
mReportedPackageName = reportedPackageName;
mResolver = resolver;
- mPackageManager = new RestrictionMockPackageManager();
+ mPackageManager = new ContactsMockPackageManager();
mPackageManager.addPackage(1000, PACKAGE_GREY);
mPackageManager.addPackage(2000, PACKAGE_RED);
mPackageManager.addPackage(3000, PACKAGE_GREEN);
@@ -157,6 +158,13 @@
public ContentResolver getContentResolver() {
return mResolver;
}
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = "contactsTestPackage";
+ return ai;
+ }
}
private static class RestrictionMockResources extends MockResources {
@@ -211,54 +219,19 @@
public CharSequence getText(int id) throws NotFoundException {
return mRes.getText(id);
}
+
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ return String.valueOf(resid);
+ }
}
- private static String sCallingPackage = null;
+ static String sCallingPackage = null;
void ensureCallingPackage() {
sCallingPackage = this.packageName;
}
- /**
- * Mock {@link PackageManager} that knows about a specific set of packages
- * to help test security models. Because {@link Binder#getCallingUid()}
- * can't be mocked, you'll have to find your mock-UID manually using your
- * {@link Context#getPackageName()}.
- */
- private static class RestrictionMockPackageManager extends MockPackageManager {
- private final HashMap<Integer, String> mForward = new HashMap<Integer, String>();
- private final HashMap<String, Integer> mReverse = new HashMap<String, Integer>();
-
- public RestrictionMockPackageManager() {
- }
-
- /**
- * Add a UID-to-package mapping, which is then stored internally.
- */
- public void addPackage(int packageUid, String packageName) {
- mForward.put(packageUid, packageName);
- mReverse.put(packageName, packageUid);
- }
-
- @Override
- public String getNameForUid(int uid) {
- return "name-for-uid";
- }
-
- @Override
- public String[] getPackagesForUid(int uid) {
- return new String[] { sCallingPackage };
- }
-
- @Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags) {
- ApplicationInfo info = new ApplicationInfo();
- Integer uid = mReverse.get(packageName);
- info.uid = (uid != null) ? uid : -1;
- return info;
- }
- }
-
public long createContact(boolean isRestricted, String name) {
ensureCallingPackage();
long contactId = createContact(isRestricted);
diff --git a/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
new file mode 100644
index 0000000..03a86c2
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.contacts;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.test.mock.MockPackageManager;
+import android.test.mock.MockResources;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Mock {@link PackageManager} that knows about a specific set of packages
+ * to help test security models. Because {@link Binder#getCallingUid()}
+ * can't be mocked, you'll have to find your mock-UID manually using your
+ * {@link Context#getPackageName()}.
+ */
+public class ContactsMockPackageManager extends MockPackageManager {
+ private final HashMap<Integer, String> mForward = new HashMap<Integer, String>();
+ private final HashMap<String, Integer> mReverse = new HashMap<String, Integer>();
+ private List<PackageInfo> mPackages;
+
+ public ContactsMockPackageManager() {
+ }
+
+ /**
+ * Add a UID-to-package mapping, which is then stored internally.
+ */
+ public void addPackage(int packageUid, String packageName) {
+ mForward.put(packageUid, packageName);
+ mReverse.put(packageName, packageUid);
+ }
+
+ @Override
+ public String getNameForUid(int uid) {
+ return "name-for-uid";
+ }
+
+ @Override
+ public String[] getPackagesForUid(int uid) {
+ if (mPackages != null) {
+ return new String[] { mPackages.get(0).packageName };
+ } else {
+ return new String[] { ContactsActor.sCallingPackage };
+ }
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags) {
+ ApplicationInfo info = new ApplicationInfo();
+ Integer uid = mReverse.get(packageName);
+ info.uid = (uid != null) ? uid : -1;
+ return info;
+ }
+
+ public void setInstalledPackages(List<PackageInfo> packages) {
+ this.mPackages = packages;
+ }
+
+ @Override
+ public List<PackageInfo> getInstalledPackages(int flags) {
+ return mPackages;
+ }
+
+ @Override
+ public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
+ for (PackageInfo info : mPackages) {
+ if (info.packageName.equals(packageName)) {
+ return info;
+ }
+ }
+ throw new NameNotFoundException();
+ }
+
+ @Override
+ public Resources getResourcesForApplication(String appPackageName) {
+ return new MockResources() {
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ return String.valueOf(resid);
+ }
+ };
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 5b40485..cfcb937 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -17,6 +17,7 @@
package com.android.providers.contacts;
import com.android.internal.util.ArrayUtils;
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.google.android.collect.Lists;
@@ -30,24 +31,8 @@
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
-import android.os.RemoteException;
import android.provider.ContactsContract;
-import android.provider.LiveFolders;
-import android.provider.OpenableColumns;
import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.ContactCounts;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.PhoneticNameStyle;
-import android.provider.ContactsContract.ProviderStatus;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.provider.ContactsContract.Settings;
-import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -57,16 +42,33 @@
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.FullNameStyle;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.PhoneticNameStyle;
+import android.provider.ContactsContract.ProviderStatus;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
+import android.provider.ContactsContract.SearchSnippetColumns;
+import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.StatusUpdates;
+import android.provider.LiveFolders;
+import android.provider.OpenableColumns;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.LargeTest;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.text.Collator;
import java.util.Arrays;
import java.util.Locale;
-
/**
* Unit tests for {@link ContactsProvider2}.
*
@@ -79,6 +81,515 @@
@LargeTest
public class ContactsProvider2Test extends BaseContactsProvider2Test {
+ public void testContactsProjection() {
+ assertProjection(Contacts.CONTENT_URI, new String[]{
+ Contacts._ID,
+ Contacts.DISPLAY_NAME_PRIMARY,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHONETIC_NAME_STYLE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.SORT_KEY_ALTERNATIVE,
+ Contacts.LAST_TIME_CONTACTED,
+ Contacts.TIMES_CONTACTED,
+ Contacts.STARRED,
+ Contacts.IN_VISIBLE_GROUP,
+ Contacts.PHOTO_ID,
+ Contacts.PHOTO_URI,
+ Contacts.PHOTO_THUMBNAIL_URI,
+ Contacts.CUSTOM_RINGTONE,
+ Contacts.HAS_PHONE_NUMBER,
+ Contacts.SEND_TO_VOICEMAIL,
+ Contacts.LOOKUP_KEY,
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_CHAT_CAPABILITY,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_ICON,
+ });
+ }
+
+ public void testContactsWithSnippetProjection() {
+ assertProjection(Contacts.CONTENT_FILTER_URI.buildUpon().appendPath("nothing").build(),
+ new String[]{
+ Contacts._ID,
+ Contacts.DISPLAY_NAME_PRIMARY,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHONETIC_NAME_STYLE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.SORT_KEY_ALTERNATIVE,
+ Contacts.LAST_TIME_CONTACTED,
+ Contacts.TIMES_CONTACTED,
+ Contacts.STARRED,
+ Contacts.IN_VISIBLE_GROUP,
+ Contacts.PHOTO_ID,
+ Contacts.PHOTO_URI,
+ Contacts.PHOTO_THUMBNAIL_URI,
+ Contacts.CUSTOM_RINGTONE,
+ Contacts.HAS_PHONE_NUMBER,
+ Contacts.SEND_TO_VOICEMAIL,
+ Contacts.LOOKUP_KEY,
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_CHAT_CAPABILITY,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_ICON,
+
+ SearchSnippetColumns.SNIPPET_MIMETYPE,
+ SearchSnippetColumns.SNIPPET_DATA_ID,
+ SearchSnippetColumns.SNIPPET_DATA1,
+ SearchSnippetColumns.SNIPPET_DATA2,
+ SearchSnippetColumns.SNIPPET_DATA3,
+ SearchSnippetColumns.SNIPPET_DATA4,
+ });
+ }
+
+ public void testRawContactsProjection() {
+ assertProjection(RawContacts.CONTENT_URI, new String[]{
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.SOURCE_ID,
+ RawContacts.VERSION,
+ RawContacts.DIRTY,
+ RawContacts.DELETED,
+ RawContacts.DISPLAY_NAME_PRIMARY,
+ RawContacts.DISPLAY_NAME_ALTERNATIVE,
+ RawContacts.DISPLAY_NAME_SOURCE,
+ RawContacts.PHONETIC_NAME,
+ RawContacts.PHONETIC_NAME_STYLE,
+ RawContacts.NAME_VERIFIED,
+ RawContacts.SORT_KEY_PRIMARY,
+ RawContacts.SORT_KEY_ALTERNATIVE,
+ RawContacts.TIMES_CONTACTED,
+ RawContacts.LAST_TIME_CONTACTED,
+ RawContacts.CUSTOM_RINGTONE,
+ RawContacts.SEND_TO_VOICEMAIL,
+ RawContacts.STARRED,
+ RawContacts.AGGREGATION_MODE,
+ RawContacts.SYNC1,
+ RawContacts.SYNC2,
+ RawContacts.SYNC3,
+ RawContacts.SYNC4,
+ });
+ }
+
+ public void testDataProjection() {
+ assertProjection(Data.CONTENT_URI, new String[]{
+ Data._ID,
+ Data.RAW_CONTACT_ID,
+ Data.DATA_VERSION,
+ Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY,
+ Data.RES_PACKAGE,
+ Data.MIMETYPE,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4,
+ Data.CONTACT_ID,
+ Data.PRESENCE,
+ Data.CHAT_CAPABILITY,
+ Data.STATUS,
+ Data.STATUS_TIMESTAMP,
+ Data.STATUS_RES_PACKAGE,
+ Data.STATUS_LABEL,
+ Data.STATUS_ICON,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.SOURCE_ID,
+ RawContacts.VERSION,
+ RawContacts.DIRTY,
+ RawContacts.NAME_VERIFIED,
+ Contacts._ID,
+ Contacts.DISPLAY_NAME_PRIMARY,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHONETIC_NAME_STYLE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.SORT_KEY_ALTERNATIVE,
+ Contacts.LAST_TIME_CONTACTED,
+ Contacts.TIMES_CONTACTED,
+ Contacts.STARRED,
+ Contacts.IN_VISIBLE_GROUP,
+ Contacts.PHOTO_ID,
+ Contacts.PHOTO_URI,
+ Contacts.PHOTO_THUMBNAIL_URI,
+ Contacts.CUSTOM_RINGTONE,
+ Contacts.SEND_TO_VOICEMAIL,
+ Contacts.LOOKUP_KEY,
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.HAS_PHONE_NUMBER,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_CHAT_CAPABILITY,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_ICON,
+ GroupMembership.GROUP_SOURCE_ID,
+ });
+ }
+
+ public void testDistinctDataProjection() {
+ assertProjection(Phone.CONTENT_FILTER_URI.buildUpon().appendPath("123").build(),
+ new String[]{
+ Data._ID,
+ Data.DATA_VERSION,
+ Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY,
+ Data.RES_PACKAGE,
+ Data.MIMETYPE,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4,
+ Data.CONTACT_ID,
+ Data.PRESENCE,
+ Data.CHAT_CAPABILITY,
+ Data.STATUS,
+ Data.STATUS_TIMESTAMP,
+ Data.STATUS_RES_PACKAGE,
+ Data.STATUS_LABEL,
+ Data.STATUS_ICON,
+ Contacts._ID,
+ Contacts.DISPLAY_NAME_PRIMARY,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHONETIC_NAME_STYLE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.SORT_KEY_ALTERNATIVE,
+ Contacts.LAST_TIME_CONTACTED,
+ Contacts.TIMES_CONTACTED,
+ Contacts.STARRED,
+ Contacts.IN_VISIBLE_GROUP,
+ Contacts.PHOTO_ID,
+ Contacts.PHOTO_URI,
+ Contacts.PHOTO_THUMBNAIL_URI,
+ Contacts.HAS_PHONE_NUMBER,
+ Contacts.CUSTOM_RINGTONE,
+ Contacts.SEND_TO_VOICEMAIL,
+ Contacts.LOOKUP_KEY,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_CHAT_CAPABILITY,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_ICON,
+ GroupMembership.GROUP_SOURCE_ID,
+ });
+ }
+
+ public void testEntityProjection() {
+ assertProjection(
+ Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, 0),
+ Contacts.Entity.CONTENT_DIRECTORY),
+ new String[]{
+ Contacts.Entity._ID,
+ Contacts.Entity.DATA_ID,
+ Contacts.Entity.RAW_CONTACT_ID,
+ Data.DATA_VERSION,
+ Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY,
+ Data.RES_PACKAGE,
+ Data.MIMETYPE,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4,
+ Data.CONTACT_ID,
+ Data.PRESENCE,
+ Data.CHAT_CAPABILITY,
+ Data.STATUS,
+ Data.STATUS_TIMESTAMP,
+ Data.STATUS_RES_PACKAGE,
+ Data.STATUS_LABEL,
+ Data.STATUS_ICON,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.SOURCE_ID,
+ RawContacts.VERSION,
+ RawContacts.DELETED,
+ RawContacts.DIRTY,
+ RawContacts.NAME_VERIFIED,
+ RawContacts.SYNC1,
+ RawContacts.SYNC2,
+ RawContacts.SYNC3,
+ RawContacts.SYNC4,
+ RawContacts.IS_RESTRICTED,
+ Contacts._ID,
+ Contacts.DISPLAY_NAME_PRIMARY,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHONETIC_NAME_STYLE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.SORT_KEY_ALTERNATIVE,
+ Contacts.LAST_TIME_CONTACTED,
+ Contacts.TIMES_CONTACTED,
+ Contacts.STARRED,
+ Contacts.IN_VISIBLE_GROUP,
+ Contacts.PHOTO_ID,
+ Contacts.PHOTO_URI,
+ Contacts.PHOTO_THUMBNAIL_URI,
+ Contacts.CUSTOM_RINGTONE,
+ Contacts.SEND_TO_VOICEMAIL,
+ Contacts.LOOKUP_KEY,
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.HAS_PHONE_NUMBER,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_CHAT_CAPABILITY,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_ICON,
+ GroupMembership.GROUP_SOURCE_ID,
+ });
+ }
+
+ public void testRawEntityProjection() {
+ assertProjection(RawContactsEntity.CONTENT_URI, new String[]{
+ RawContacts.Entity.DATA_ID,
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.SOURCE_ID,
+ RawContacts.VERSION,
+ RawContacts.DIRTY,
+ RawContacts.NAME_VERIFIED,
+ RawContacts.DELETED,
+ RawContacts.IS_RESTRICTED,
+ RawContacts.SYNC1,
+ RawContacts.SYNC2,
+ RawContacts.SYNC3,
+ RawContacts.SYNC4,
+ RawContacts.STARRED,
+ Data.DATA_VERSION,
+ Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY,
+ Data.RES_PACKAGE,
+ Data.MIMETYPE,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4,
+ GroupMembership.GROUP_SOURCE_ID,
+ });
+ }
+
+ public void testPhoneLookupProjection() {
+ assertProjection(PhoneLookup.CONTENT_FILTER_URI.buildUpon().appendPath("123").build(),
+ new String[]{
+ PhoneLookup._ID,
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.LAST_TIME_CONTACTED,
+ PhoneLookup.TIMES_CONTACTED,
+ PhoneLookup.STARRED,
+ PhoneLookup.IN_VISIBLE_GROUP,
+ PhoneLookup.PHOTO_ID,
+ PhoneLookup.PHOTO_URI,
+ PhoneLookup.PHOTO_THUMBNAIL_URI,
+ PhoneLookup.CUSTOM_RINGTONE,
+ PhoneLookup.HAS_PHONE_NUMBER,
+ PhoneLookup.SEND_TO_VOICEMAIL,
+ PhoneLookup.NUMBER,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.NORMALIZED_NUMBER,
+ });
+ }
+
+ public void testGroupsProjection() {
+ assertProjection(Groups.CONTENT_URI, new String[]{
+ Groups._ID,
+ Groups.ACCOUNT_NAME,
+ Groups.ACCOUNT_TYPE,
+ Groups.SOURCE_ID,
+ Groups.DIRTY,
+ Groups.VERSION,
+ Groups.RES_PACKAGE,
+ Groups.TITLE,
+ Groups.TITLE_RES,
+ Groups.GROUP_VISIBLE,
+ Groups.SYSTEM_ID,
+ Groups.DELETED,
+ Groups.NOTES,
+ Groups.SHOULD_SYNC,
+ Groups.FAVORITES,
+ Groups.AUTO_ADD,
+ Groups.SYNC1,
+ Groups.SYNC2,
+ Groups.SYNC3,
+ Groups.SYNC4,
+ });
+ }
+
+ public void testGroupsSummaryProjection() {
+ assertProjection(Groups.CONTENT_SUMMARY_URI, new String[]{
+ Groups._ID,
+ Groups.ACCOUNT_NAME,
+ Groups.ACCOUNT_TYPE,
+ Groups.SOURCE_ID,
+ Groups.DIRTY,
+ Groups.VERSION,
+ Groups.RES_PACKAGE,
+ Groups.TITLE,
+ Groups.TITLE_RES,
+ Groups.GROUP_VISIBLE,
+ Groups.SYSTEM_ID,
+ Groups.DELETED,
+ Groups.NOTES,
+ Groups.SHOULD_SYNC,
+ Groups.FAVORITES,
+ Groups.AUTO_ADD,
+ Groups.SYNC1,
+ Groups.SYNC2,
+ Groups.SYNC3,
+ Groups.SYNC4,
+ Groups.SUMMARY_COUNT,
+ Groups.SUMMARY_WITH_PHONES,
+ });
+ }
+
+ public void testAggregateExceptionProjection() {
+ assertProjection(AggregationExceptions.CONTENT_URI, new String[]{
+ AggregationExceptionColumns._ID,
+ AggregationExceptions.TYPE,
+ AggregationExceptions.RAW_CONTACT_ID1,
+ AggregationExceptions.RAW_CONTACT_ID2,
+ });
+ }
+
+ public void testSettingsProjection() {
+ assertProjection(Settings.CONTENT_URI, new String[]{
+ Settings.ACCOUNT_NAME,
+ Settings.ACCOUNT_TYPE,
+ Settings.UNGROUPED_VISIBLE,
+ Settings.SHOULD_SYNC,
+ Settings.ANY_UNSYNCED,
+ Settings.UNGROUPED_COUNT,
+ Settings.UNGROUPED_WITH_PHONES,
+ });
+ }
+
+ public void testStatusUpdatesProjection() {
+ assertProjection(StatusUpdates.CONTENT_URI, new String[]{
+ PresenceColumns.RAW_CONTACT_ID,
+ StatusUpdates.DATA_ID,
+ StatusUpdates.IM_ACCOUNT,
+ StatusUpdates.IM_HANDLE,
+ StatusUpdates.PROTOCOL,
+ StatusUpdates.CUSTOM_PROTOCOL,
+ StatusUpdates.PRESENCE,
+ StatusUpdates.CHAT_CAPABILITY,
+ StatusUpdates.STATUS,
+ StatusUpdates.STATUS_TIMESTAMP,
+ StatusUpdates.STATUS_RES_PACKAGE,
+ StatusUpdates.STATUS_ICON,
+ StatusUpdates.STATUS_LABEL,
+ });
+ }
+
+ public void testLiveFoldersProjection() {
+ assertProjection(
+ Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "live_folders/contacts"),
+ new String[]{
+ LiveFolders._ID,
+ LiveFolders.NAME,
+ });
+ }
+
+ public void testDirectoryProjection() {
+ assertProjection(Directory.CONTENT_URI, new String[]{
+ Directory._ID,
+ Directory.PACKAGE_NAME,
+ Directory.TYPE_RESOURCE_ID,
+ Directory.DISPLAY_NAME,
+ Directory.DIRECTORY_AUTHORITY,
+ Directory.ACCOUNT_TYPE,
+ Directory.ACCOUNT_NAME,
+ Directory.EXPORT_SUPPORT,
+ Directory.SHORTCUT_SUPPORT,
+ Directory.PHOTO_SUPPORT,
+ });
+ }
+
public void testRawContactsInsert() {
ContentValues values = new ContentValues();
@@ -106,6 +617,169 @@
assertNetworkNotified(true);
}
+ public void testDataDirectoryWithLookupUri() {
+ ContentValues values = new ContentValues();
+
+ long rawContactId = createRawContactWithName();
+ insertPhoneNumber(rawContactId, "555-GOOG-411");
+ insertEmail(rawContactId, "google@android.com");
+
+ long contactId = queryContactId(rawContactId);
+ String lookupKey = queryLookupKey(contactId);
+
+ // Complete and valid lookup URI
+ Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+ Uri dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY);
+
+ assertDataRows(dataUri, values);
+
+ // Complete but stale lookup URI
+ lookupUri = ContactsContract.Contacts.getLookupUri(contactId + 1, lookupKey);
+ dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY);
+ assertDataRows(dataUri, values);
+
+ // Incomplete lookup URI (lookup key only, no contact ID)
+ dataUri = Uri.withAppendedPath(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
+ lookupKey), Contacts.Data.CONTENT_DIRECTORY);
+ assertDataRows(dataUri, values);
+ }
+
+ private void assertDataRows(Uri dataUri, ContentValues values) {
+ Cursor cursor = mResolver.query(dataUri, new String[]{ Data.DATA1 }, null, null, Data._ID);
+ assertEquals(3, cursor.getCount());
+ cursor.moveToFirst();
+ values.put(Data.DATA1, "John Doe");
+ assertCursorValues(cursor, values);
+
+ cursor.moveToNext();
+ values.put(Data.DATA1, "555-GOOG-411");
+ assertCursorValues(cursor, values);
+
+ cursor.moveToNext();
+ values.put(Data.DATA1, "google@android.com");
+ assertCursorValues(cursor, values);
+
+ cursor.close();
+ }
+
+ public void testContactEntitiesWithIdBasedUri() {
+ ContentValues values = new ContentValues();
+ Account account1 = new Account("act1", "actype1");
+ Account account2 = new Account("act2", "actype2");
+
+ long rawContactId1 = createRawContactWithName(account1);
+ insertImHandle(rawContactId1, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk");
+ insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", StatusUpdates.IDLE, "Busy", 90,
+ StatusUpdates.CAPABILITY_HAS_CAMERA);
+
+ long rawContactId2 = createRawContact(account2);
+ setAggregationException(
+ AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
+
+ long contactId = queryContactId(rawContactId1);
+
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
+
+ assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
+ }
+
+ public void testContactEntitiesWithLookupUri() {
+ ContentValues values = new ContentValues();
+ Account account1 = new Account("act1", "actype1");
+ Account account2 = new Account("act2", "actype2");
+
+ long rawContactId1 = createRawContactWithName(account1);
+ insertImHandle(rawContactId1, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk");
+ insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", StatusUpdates.IDLE, "Busy", 90,
+ StatusUpdates.CAPABILITY_HAS_CAMERA);
+
+ long rawContactId2 = createRawContact(account2);
+ setAggregationException(
+ AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
+
+ long contactId = queryContactId(rawContactId1);
+ String lookupKey = queryLookupKey(contactId);
+
+ // First try with a matching contact ID
+ Uri contactLookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+ Uri entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY);
+ assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
+
+ // Now try with a contact ID mismatch
+ contactLookupUri = ContactsContract.Contacts.getLookupUri(contactId + 1, lookupKey);
+ entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY);
+ assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
+
+ // Now try without an ID altogether
+ contactLookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
+ entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY);
+ assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
+ }
+
+ private void assertEntityRows(Uri entityUri, long contactId, long rawContactId1,
+ long rawContactId2) {
+ ContentValues values = new ContentValues();
+
+ Cursor cursor = mResolver.query(entityUri, null, null, null,
+ Contacts.Entity.RAW_CONTACT_ID + "," + Contacts.Entity.DATA_ID);
+ assertEquals(3, cursor.getCount());
+
+ // First row - name
+ cursor.moveToFirst();
+ values.put(Contacts.Entity.CONTACT_ID, contactId);
+ values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId1);
+ values.put(Contacts.Entity.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+ values.put(Contacts.Entity.DATA1, "John Doe");
+ values.put(Contacts.Entity.ACCOUNT_NAME, "act1");
+ values.put(Contacts.Entity.ACCOUNT_TYPE, "actype1");
+ values.put(Contacts.Entity.DISPLAY_NAME, "John Doe");
+ values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John");
+ values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1);
+ values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
+ values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE);
+ values.put(Contacts.Entity.CONTACT_STATUS, "Busy");
+ values.putNull(Contacts.Entity.PRESENCE);
+ assertCursorValues(cursor, values);
+
+ // Second row - IM
+ cursor.moveToNext();
+ values.put(Contacts.Entity.CONTACT_ID, contactId);
+ values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId1);
+ values.put(Contacts.Entity.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+ values.put(Contacts.Entity.DATA1, "gtalk");
+ values.put(Contacts.Entity.ACCOUNT_NAME, "act1");
+ values.put(Contacts.Entity.ACCOUNT_TYPE, "actype1");
+ values.put(Contacts.Entity.DISPLAY_NAME, "John Doe");
+ values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John");
+ values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1);
+ values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
+ values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE);
+ values.put(Contacts.Entity.CONTACT_STATUS, "Busy");
+ values.put(Contacts.Entity.PRESENCE, StatusUpdates.IDLE);
+ assertCursorValues(cursor, values);
+
+ // Third row - second raw contact, not data
+ cursor.moveToNext();
+ values.put(Contacts.Entity.CONTACT_ID, contactId);
+ values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId2);
+ values.putNull(Contacts.Entity.MIMETYPE);
+ values.putNull(Contacts.Entity.DATA_ID);
+ values.putNull(Contacts.Entity.DATA1);
+ values.put(Contacts.Entity.ACCOUNT_NAME, "act2");
+ values.put(Contacts.Entity.ACCOUNT_TYPE, "actype2");
+ values.put(Contacts.Entity.DISPLAY_NAME, "John Doe");
+ values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John");
+ values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1);
+ values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
+ values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE);
+ values.put(Contacts.Entity.CONTACT_STATUS, "Busy");
+ values.putNull(Contacts.Entity.PRESENCE);
+ assertCursorValues(cursor, values);
+
+ cursor.close();
+ }
+
public void testDataInsert() {
long rawContactId = createRawContactWithName("John", "Doe");
@@ -237,16 +911,77 @@
values.put(PhoneLookup.SEND_TO_VOICEMAIL, 1);
assertStoredValues(lookupUri1, values);
- // The strict comparation, adopted in Donut, does not allow the behavior like
- // "8004664411 == 4664411", while the loose comparation, which had been used in Cupcake
- // and reverted back into the default in Eclair, allows it. Hmm...
- final boolean useStrictComparation =
- mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_use_strict_phone_number_comparation);
- final int expectedResult = (useStrictComparation ? 0 : 1);
-
+ // In the context that 8004664411 is a valid number, "4664411" as a
+ // call id should not match to "8004664411"
Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "4664411");
- assertEquals(expectedResult, getCount(lookupUri2, null, null));
+ assertEquals(0, getCount(lookupUri2, null, null));
+ }
+
+ public void testPhoneLookupUseCases() {
+ ContentValues values = new ContentValues();
+ Uri rawContactUri;
+ long rawContactId;
+ Uri lookupUri2;
+
+ values.put(RawContacts.CUSTOM_RINGTONE, "d");
+ values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
+
+ // International format in contacts
+ rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ rawContactId = ContentUris.parseId(rawContactUri);
+
+ insertStructuredName(rawContactId, "Hot", "Tamale");
+ insertPhoneNumber(rawContactId, "+1-650-861-0000");
+
+ values.clear();
+
+ // match with international format
+ lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0000");
+ assertEquals(1, getCount(lookupUri2, null, null));
+
+ // match with national format
+ lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0000");
+ assertEquals(1, getCount(lookupUri2, null, null));
+
+ // National format in contacts
+ values.clear();
+ values.put(RawContacts.CUSTOM_RINGTONE, "d");
+ values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
+ rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ rawContactId = ContentUris.parseId(rawContactUri);
+
+ insertStructuredName(rawContactId, "Hot1", "Tamale");
+ insertPhoneNumber(rawContactId, "650-861-0001");
+
+ values.clear();
+
+ // match with international format
+ lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0001");
+ assertEquals(2, getCount(lookupUri2, null, null));
+
+ // match with national format
+ lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0001");
+ assertEquals(2, getCount(lookupUri2, null, null));
+
+ // Local format in contacts
+ values.clear();
+ values.put(RawContacts.CUSTOM_RINGTONE, "d");
+ values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
+ rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ rawContactId = ContentUris.parseId(rawContactUri);
+
+ insertStructuredName(rawContactId, "Hot2", "Tamale");
+ insertPhoneNumber(rawContactId, "861-0002");
+
+ values.clear();
+
+ // match with international format
+ lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0002");
+ assertEquals(1, getCount(lookupUri2, null, null));
+
+ // match with national format
+ lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0002");
+ assertEquals(1, getCount(lookupUri2, null, null));
}
public void testPhoneUpdate() {
@@ -367,6 +1102,93 @@
assertEquals(0, getCount(filterUri5, null, null));
}
+ public void testEmailFilterSortOrder() {
+
+ // Adding contacts from the end to beginning of the expected order.
+
+ // Never contacted
+ insertContactWithEmail("never", false);
+ insertContactWithEmail("starred-never", true);
+
+ // Contacted a long time ago
+ insertContactWithEmail("a-longago", 10, 1800, false);
+ insertContactWithEmail("b-longago", 20, 1000, false);
+ insertContactWithEmail("c-longago", 30, 2000, false);
+
+ // Contacted fairly recently
+ insertContactWithEmail("a-recent", 10, 18, false);
+ insertContactWithEmail("b-recent", 20, 10, false);
+ insertContactWithEmail("c-recent", 30, 20, false);
+
+ // Contacted very recently
+ insertContactWithEmail("a-current", 10, 1, false);
+ insertContactWithEmail("b-current", 20, 0, false);
+ insertContactWithEmail("c-current", 30, 2, false);
+
+ // Starred
+ insertContactWithEmail("starred-longago", 10, 100, true);
+ insertContactWithEmail("starred-current", 10, 10, true);
+ insertContactWithEmail("starred-recent", 10, 1, true);
+
+ Uri filterUri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "findme");
+ Cursor cursor = mResolver.query(filterUri, new String[]{Contacts.DISPLAY_NAME},
+ null, null, null);
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "starred-recent");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "starred-current");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "starred-longago");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "starred-never");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "c-current");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "b-current");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "a-current");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "c-recent");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "b-recent");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "a-recent");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "c-longago");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "b-longago");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "a-longago");
+ cursor.moveToNext();
+ assertCursorValue(cursor, Contacts.DISPLAY_NAME, "never");
+ cursor.close();
+ }
+
+ private void insertContactWithEmail(String name, boolean starred) {
+ long rawContactId = createRawContactWithName(name, null);
+ long contactId = queryContactId(rawContactId);
+ if (starred) {
+ storeValue(Contacts.CONTENT_URI, contactId, Contacts.STARRED, 1);
+ }
+ insertEmail(rawContactId, "findme" + name + "@acme.com");
+ }
+
+ private void insertContactWithEmail(
+ String name, int timesContacted, int lastTimeContactedDays, boolean starred) {
+ long rawContactId = createRawContactWithName(name, null);
+ long contactId = queryContactId(rawContactId);
+ ContentValues values = new ContentValues();
+ values.put(Contacts.TIMES_CONTACTED, timesContacted);
+ values.put(Contacts.LAST_TIME_CONTACTED,
+ System.currentTimeMillis() - (lastTimeContactedDays * 24 * 60 * 60 * 1000l));
+ if (starred) {
+ values.put(Contacts.STARRED, 1);
+ }
+ mResolver.update(
+ ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), values, null, null);
+ insertEmail(rawContactId, "findme" + name + "@acme.com");
+ }
+
public void testPostalsQuery() {
long rawContactId = createRawContactWithName("Alice", "Nextore");
Uri dataUri = insertPostalAddress(rawContactId, "1600 Amphiteatre Ave, Mountain View");
@@ -390,7 +1212,7 @@
ContentValues values = new ContentValues();
long contactId = createContact(values, "John", "Doe",
"18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
- StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY);
+ StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
assertStoredValues(contactUri, values);
@@ -413,7 +1235,7 @@
ContentValues values = new ContentValues();
long rawContactId = createRawContact(values, "18004664411",
"goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
- StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY |
+ StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO |
StatusUpdates.CAPABILITY_HAS_VOICE);
ContentValues nameValues = new ContentValues();
@@ -455,7 +1277,7 @@
ContentValues values1 = new ContentValues();
createContact(values1, "Noah", "Tever", "18004664411",
"a@acme.com", StatusUpdates.OFFLINE, 0, 0, 0,
- StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY);
+ StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
ContentValues values2 = new ContentValues();
createContact(values2, "Sam", "Times", "18004664412",
"b@acme.com", StatusUpdates.INVISIBLE, 3, 0, 0,
@@ -463,11 +1285,11 @@
ContentValues values3 = new ContentValues();
createContact(values3, "Lotta", "Calling", "18004664413",
"c@acme.com", StatusUpdates.AWAY, 5, 0, 0,
- StatusUpdates.CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY);
+ StatusUpdates.CAPABILITY_HAS_VIDEO);
ContentValues values4 = new ContentValues();
createContact(values4, "Fay", "Veritt", "18004664414",
"d@acme.com", StatusUpdates.AVAILABLE, 0, 1, 0,
- StatusUpdates.CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY | StatusUpdates.CAPABILITY_HAS_VOICE);
+ StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE);
Cursor c = mResolver.query(Contacts.CONTENT_STREQUENT_URI, null, null, null,
Contacts._ID);
@@ -528,7 +1350,6 @@
insertStructuredName(rawContactId, "John", "Doe");
Uri photoUri = insertPhoto(rawContactId);
long photoId = ContentUris.parseId(photoUri);
- values.put(Contacts.PHOTO_ID, photoId);
insertPhoneNumber(rawContactId, "18004664411");
insertPhoneNumber(rawContactId, "18004664412");
insertEmail(rawContactId, "goog411@acme.com");
@@ -1238,7 +2059,7 @@
// Match on IM (custom)
insertStatusUpdate(Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im", StatusUpdates.IDLE, "Idle",
- StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY);
+ StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
// Match on Email
insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "m@acme.com", StatusUpdates.AWAY, "Away",
@@ -1399,7 +2220,7 @@
ContentValues values = new ContentValues();
values.put(StatusUpdates.PROTOCOL, protocol);
values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol);
- values.put(StatusUpdates.PRESENCE_STATUS, presence);
+ values.put(StatusUpdates.PRESENCE, presence);
values.put(StatusUpdates.STATUS, status);
assertCursorValues(c, values);
}
@@ -1533,7 +2354,7 @@
null, Contacts.IN_VISIBLE_GROUP, expectedValue);
}
- public void testContentEntityIterator() throws RemoteException {
+ public void testContentEntityIterator() {
// create multiple contacts and check that the selected ones are returned
long id;
@@ -1794,8 +2615,8 @@
ContactsProvider2 cp = (ContactsProvider2) getProvider();
cp.onAccountsUpdated(new Account[]{mAccount, mAccountTwo});
assertEquals(1, getCount(RawContacts.CONTENT_URI, null, null));
- assertStoredValue(rawContact3, RawContacts.ACCOUNT_NAME, "account1");
- assertStoredValue(rawContact3, RawContacts.ACCOUNT_TYPE, "account type1");
+ assertStoredValue(rawContact3, RawContacts.ACCOUNT_NAME, null);
+ assertStoredValue(rawContact3, RawContacts.ACCOUNT_TYPE, null);
long rawContactId1 = createRawContact(mAccount);
insertEmail(rawContactId1, "account1@email.com");
@@ -1849,7 +2670,6 @@
// The photo should be the remaining one
assertStoredValue(Contacts.CONTENT_URI, contactId,
Contacts.PHOTO_ID, ContentUris.parseId(photoUri1));
-
}
public void testContactDeletion() {
@@ -2016,21 +2836,36 @@
Uri twigUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
queryContactId(rawContactId)), Contacts.Photo.CONTENT_DIRECTORY);
+ assertStoredValue(
+ ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)),
+ Contacts.PHOTO_URI, twigUri.toString());
+
long twigId = Long.parseLong(getStoredValue(twigUri, Data._ID));
assertEquals(ContentUris.parseId(photoUri), twigId);
}
- public void testOpenAssertFileDescriptorForPhoto() throws Exception {
+ public void testInputStreamForPhoto() throws Exception {
long rawContactId = createRawContact();
Uri photoUri = insertPhoto(rawContactId);
- AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(photoUri, "r");
- assertEquals(loadTestPhoto().length, fd.getLength());
+ assertInputStreamContent(loadTestPhoto(), mResolver.openInputStream(photoUri));
Uri contactPhotoUri = Uri.withAppendedPath(
ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)),
Contacts.Photo.CONTENT_DIRECTORY);
- fd = mResolver.openAssetFileDescriptor(contactPhotoUri, "r");
- assertEquals(loadTestPhoto().length, fd.getLength());
+ assertInputStreamContent(loadTestPhoto(), mResolver.openInputStream(contactPhotoUri));
+ }
+
+ private static void assertInputStreamContent(byte[] expected, InputStream is)
+ throws IOException {
+ try {
+ byte[] observed = new byte[expected.length];
+ int count = is.read(observed);
+ assertEquals(expected.length, count);
+ assertEquals(-1, is.read());
+ MoreAsserts.assertEquals(expected, observed);
+ } finally {
+ is.close();
+ }
}
public void testSuperPrimaryPhoto() {
@@ -2048,6 +2883,8 @@
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
queryContactId(rawContactId1));
assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId1);
+ assertStoredValue(contactUri, Contacts.PHOTO_URI,
+ Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY));
setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
rawContactId1, rawContactId2);
@@ -2466,6 +3303,380 @@
}
}
+ public void testAutoGroupMembership() {
+ long g1 = createGroup(mAccount, "g1", "t1", 0, true /* autoAdd */, false /* favorite */);
+ long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */);
+ long g3 = createGroup(mAccountTwo, "g3", "t3", 0, true /* autoAdd */, false /* favorite */);
+ long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, false/* favorite */);
+ long r1 = createRawContact(mAccount);
+ long r2 = createRawContact(mAccountTwo);
+ long r3 = createRawContact(null);
+
+ Cursor c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ c = queryGroupMemberships(mAccountTwo);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g3, c.getLong(0));
+ assertEquals(r2, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+ }
+
+ public void testNoAutoAddMembershipAfterGroupCreation() {
+ long r1 = createRawContact(mAccount);
+ long r2 = createRawContact(mAccount);
+ long r3 = createRawContact(mAccount);
+ long r4 = createRawContact(mAccountTwo);
+ long r5 = createRawContact(mAccountTwo);
+ long r6 = createRawContact(null);
+
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+
+ long g1 = createGroup(mAccount, "g1", "t1", 0, true /* autoAdd */, false /* favorite */);
+ long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */);
+ long g3 = createGroup(mAccountTwo, "g3", "t3", 0, true /* autoAdd */, false/* favorite */);
+
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ }
+
+ // create some starred and non-starred contacts, some associated with account, some not
+ // favorites group created
+ // the starred contacts should be added to group
+ // favorites group removed
+ // no change to starred status
+ public void testFavoritesMembershipAfterGroupCreation() {
+ long r1 = createRawContact(mAccount, RawContacts.STARRED, "1");
+ long r2 = createRawContact(mAccount);
+ long r3 = createRawContact(mAccount, RawContacts.STARRED, "1");
+ long r4 = createRawContact(mAccountTwo, RawContacts.STARRED, "1");
+ long r5 = createRawContact(mAccountTwo);
+ long r6 = createRawContact(null, RawContacts.STARRED, "1");
+ long r7 = createRawContact(null);
+
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+
+ long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */);
+ long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */);
+ long g3 = createGroup(mAccountTwo, "g3", "t3", 0, false /* autoAdd */, false/* favorite */);
+
+ assertTrue(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertTrue(queryRawContactIsStarred(r3));
+ assertTrue(queryRawContactIsStarred(r4));
+ assertFalse(queryRawContactIsStarred(r5));
+ assertTrue(queryRawContactIsStarred(r6));
+ assertFalse(queryRawContactIsStarred(r7));
+
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ Cursor c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r3, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ updateItem(RawContacts.CONTENT_URI, r6,
+ RawContacts.ACCOUNT_NAME, mAccount.name,
+ RawContacts.ACCOUNT_TYPE, mAccount.type);
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r3, c.getLong(1));
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r6, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ mResolver.delete(ContentUris.withAppendedId(Groups.CONTENT_URI, g1), null, null);
+
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+
+ assertTrue(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertTrue(queryRawContactIsStarred(r3));
+ assertTrue(queryRawContactIsStarred(r4));
+ assertFalse(queryRawContactIsStarred(r5));
+ assertTrue(queryRawContactIsStarred(r6));
+ assertFalse(queryRawContactIsStarred(r7));
+ }
+
+ public void testFavoritesGroupMembershipChangeAfterStarChange() {
+ long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */);
+ long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false/* favorite */);
+ long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, true /* favorite */);
+ long g5 = createGroup(mAccountTwo, "g5", "t5", 0, false /* autoAdd */, false/* favorite */);
+ long r1 = createRawContact(mAccount, RawContacts.STARRED, "1");
+ long r2 = createRawContact(mAccount);
+ long r3 = createRawContact(mAccountTwo);
+
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ Cursor c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // remove the star from r1
+ assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "0"));
+
+ // Since no raw contacts are starred, there should be no group memberships.
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+
+ // mark r1 as starred
+ assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "1"));
+ // Now that r1 is starred it should have a membership in the one groups from mAccount
+ // that is marked as a favorite.
+ // There should be no memberships in mAccountTwo since it has no starred raw contacts.
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // remove the star from r1
+ assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "0"));
+ // Since no raw contacts are starred, there should be no group memberships.
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(r1));
+ assertNotNull(contactUri);
+
+ // mark r1 as starred via its contact lookup uri
+ assertEquals(1, updateItem(contactUri, Contacts.STARRED, "1"));
+ // Now that r1 is starred it should have a membership in the one groups from mAccount
+ // that is marked as a favorite.
+ // There should be no memberships in mAccountTwo since it has no starred raw contacts.
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // remove the star from r1
+ updateItem(contactUri, Contacts.STARRED, "0");
+ // Since no raw contacts are starred, there should be no group memberships.
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ }
+
+ public void testStarChangedAfterGroupMembershipChange() {
+ long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */);
+ long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false/* favorite */);
+ long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, true /* favorite */);
+ long g5 = createGroup(mAccountTwo, "g5", "t5", 0, false /* autoAdd */, false/* favorite */);
+ long r1 = createRawContact(mAccount);
+ long r2 = createRawContact(mAccount);
+ long r3 = createRawContact(mAccountTwo);
+
+ assertFalse(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertFalse(queryRawContactIsStarred(r3));
+
+ Cursor c;
+
+ // add r1 to one favorites group
+ // r1's star should automatically be set
+ // r1 should automatically be added to the other favorites group
+ Uri urir1g1 = insertGroupMembership(r1, g1);
+ assertTrue(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertFalse(queryRawContactIsStarred(r3));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ c = queryGroupMemberships(mAccount);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g1, c.getLong(0));
+ assertEquals(r1, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // remove r1 from one favorites group
+ mResolver.delete(urir1g1, null, null);
+ // r1's star should no longer be set
+ assertFalse(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertFalse(queryRawContactIsStarred(r3));
+ // there should be no membership rows
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+
+ // add r3 to the one favorites group for that account
+ // r3's star should automatically be set
+ Uri urir3g4 = insertGroupMembership(r3, g4);
+ assertFalse(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertTrue(queryRawContactIsStarred(r3));
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ c = queryGroupMemberships(mAccountTwo);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals(g4, c.getLong(0));
+ assertEquals(r3, c.getLong(1));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // remove r3 from the favorites group
+ mResolver.delete(urir3g4, null, null);
+ // r3's star should automatically be cleared
+ assertFalse(queryRawContactIsStarred(r1));
+ assertFalse(queryRawContactIsStarred(r2));
+ assertFalse(queryRawContactIsStarred(r3));
+ assertNoRowsAndClose(queryGroupMemberships(mAccount));
+ assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
+ }
+
+ public void testReadOnlyRawContact() {
+ long rawContactId = createRawContact();
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ storeValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "first");
+ storeValue(rawContactUri, RawContacts.RAW_CONTACT_IS_READ_ONLY, 1);
+
+ storeValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "second");
+ assertStoredValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "first");
+
+ Uri syncAdapterUri = rawContactUri.buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "1")
+ .build();
+ storeValue(syncAdapterUri, RawContacts.CUSTOM_RINGTONE, "third");
+ assertStoredValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "third");
+ }
+
+ public void testReadOnlyDataRow() {
+ long rawContactId = createRawContact();
+ Uri emailUri = insertEmail(rawContactId, "email");
+ Uri phoneUri = insertPhoneNumber(rawContactId, "555-1111");
+
+ storeValue(emailUri, Data.IS_READ_ONLY, "1");
+ storeValue(emailUri, Email.ADDRESS, "changed");
+ storeValue(phoneUri, Phone.NUMBER, "555-2222");
+ assertStoredValue(emailUri, Email.ADDRESS, "email");
+ assertStoredValue(phoneUri, Phone.NUMBER, "555-2222");
+
+ Uri syncAdapterUri = emailUri.buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "1")
+ .build();
+ storeValue(syncAdapterUri, Email.ADDRESS, "changed");
+ assertStoredValue(emailUri, Email.ADDRESS, "changed");
+ }
+
+ public void testContactWithReadOnlyRawContact() {
+ long rawContactId1 = createRawContact();
+ Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
+ storeValue(rawContactUri1, RawContacts.CUSTOM_RINGTONE, "first");
+
+ long rawContactId2 = createRawContact();
+ Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2);
+ storeValue(rawContactUri2, RawContacts.CUSTOM_RINGTONE, "second");
+ storeValue(rawContactUri2, RawContacts.RAW_CONTACT_IS_READ_ONLY, 1);
+
+ setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
+ rawContactId1, rawContactId2);
+
+ long contactId = queryContactId(rawContactId1);
+
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ storeValue(contactUri, Contacts.CUSTOM_RINGTONE, "rt");
+ assertStoredValue(contactUri, Contacts.CUSTOM_RINGTONE, "rt");
+ assertStoredValue(rawContactUri1, RawContacts.CUSTOM_RINGTONE, "rt");
+ assertStoredValue(rawContactUri2, RawContacts.CUSTOM_RINGTONE, "second");
+ }
+
+ public void testNameParsingQuery() {
+ Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name")
+ .appendQueryParameter(StructuredName.DISPLAY_NAME, "Mr. John Q. Doe Jr.").build();
+ Cursor cursor = mResolver.query(uri, null, null, null, null);
+ ContentValues values = new ContentValues();
+ values.put(StructuredName.DISPLAY_NAME, "Mr. John Q. Doe Jr.");
+ values.put(StructuredName.PREFIX, "Mr");
+ values.put(StructuredName.GIVEN_NAME, "John");
+ values.put(StructuredName.MIDDLE_NAME, "Q.");
+ values.put(StructuredName.FAMILY_NAME, "Doe");
+ values.put(StructuredName.SUFFIX, "Jr.");
+ values.put(StructuredName.FULL_NAME_STYLE, FullNameStyle.WESTERN);
+ assertTrue(cursor.moveToFirst());
+ assertCursorValues(cursor, values);
+ cursor.close();
+ }
+
+ public void testNameConcatenationQuery() {
+ Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name")
+ .appendQueryParameter(StructuredName.PREFIX, "Mr")
+ .appendQueryParameter(StructuredName.GIVEN_NAME, "John")
+ .appendQueryParameter(StructuredName.MIDDLE_NAME, "Q.")
+ .appendQueryParameter(StructuredName.FAMILY_NAME, "Doe")
+ .appendQueryParameter(StructuredName.SUFFIX, "Jr.")
+ .build();
+ Cursor cursor = mResolver.query(uri, null, null, null, null);
+ ContentValues values = new ContentValues();
+ values.put(StructuredName.DISPLAY_NAME, "John Q. Doe, Jr.");
+ values.put(StructuredName.PREFIX, "Mr");
+ values.put(StructuredName.GIVEN_NAME, "John");
+ values.put(StructuredName.MIDDLE_NAME, "Q.");
+ values.put(StructuredName.FAMILY_NAME, "Doe");
+ values.put(StructuredName.SUFFIX, "Jr.");
+ values.put(StructuredName.FULL_NAME_STYLE, FullNameStyle.WESTERN);
+ assertTrue(cursor.moveToFirst());
+ assertCursorValues(cursor, values);
+ cursor.close();
+ }
+
+ private Cursor queryGroupMemberships(Account account) {
+ Cursor c = mResolver.query(maybeAddAccountQueryParameters(Data.CONTENT_URI, account),
+ new String[]{GroupMembership.GROUP_ROW_ID, GroupMembership.RAW_CONTACT_ID},
+ Data.MIMETYPE + "=?", new String[]{GroupMembership.CONTENT_ITEM_TYPE},
+ GroupMembership.GROUP_SOURCE_ID);
+ return c;
+ }
+
private String readToEnd(FileInputStream inputStream) {
try {
int ch;
diff --git a/tests/src/com/android/providers/contacts/DirectoryTest.java b/tests/src/com/android/providers/contacts/DirectoryTest.java
new file mode 100644
index 0000000..5831612
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/DirectoryTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+package com.android.providers.contacts;
+
+import android.accounts.Account;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.test.suitebuilder.annotation.LargeTest;
+
+
+/**
+ * Unit tests for {@link ContactsProvider2}, directory functionality.
+ *
+ * Run the test like this:
+ * <code>
+ * adb shell am instrument -e class com.android.providers.contacts.DirectoryTest -w \
+ * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
+ * </code>
+ */
+@LargeTest
+public class DirectoryTest extends BaseContactsProvider2Test {
+
+ public void testDefaultDirectory() {
+ ContentValues values = new ContentValues();
+ Uri defaultDirectoryUri =
+ ContentUris.withAppendedId(Directory.CONTENT_URI, Directory.DEFAULT);
+
+ values.put(Directory.PACKAGE_NAME, "contactsTestPackage");
+ values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
+ values.put(Directory.TYPE_RESOURCE_ID, R.string.default_directory);
+ values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
+ values.putNull(Directory.ACCOUNT_NAME);
+ values.putNull(Directory.ACCOUNT_TYPE);
+ values.putNull(Directory.DISPLAY_NAME);
+
+ assertStoredValues(defaultDirectoryUri, values);
+ }
+
+ public void testInvisibleLocalDirectory() {
+ ContentValues values = new ContentValues();
+ Uri defaultDirectoryUri =
+ ContentUris.withAppendedId(Directory.CONTENT_URI, Directory.LOCAL_INVISIBLE);
+
+ values.put(Directory.PACKAGE_NAME, "contactsTestPackage");
+ values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
+ values.put(Directory.TYPE_RESOURCE_ID, R.string.local_invisible_directory);
+ values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
+ values.putNull(Directory.ACCOUNT_NAME);
+ values.putNull(Directory.ACCOUNT_TYPE);
+ values.putNull(Directory.DISPLAY_NAME);
+
+ assertStoredValues(defaultDirectoryUri, values);
+ }
+
+ public void testForwardingToLocalContacts() {
+ long contactId = queryContactId(createRawContactWithName("John", "Doe"));
+
+ Uri contentUri = Contacts.CONTENT_URI.buildUpon().appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)).build();
+
+ Cursor cursor = mResolver.query(contentUri,
+ new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(contactId, cursor.getLong(0));
+ assertEquals("John Doe", cursor.getString(1));
+ cursor.close();
+ }
+
+ public void testForwardingToLocalInvisibleContacts() {
+
+ // Visible because there is no account
+ long contactId1 = queryContactId(createRawContactWithName("Bob", "Parr"));
+
+ Account account = new Account("accountName", "accountType");
+ long groupId = createGroup(account, "sid", "def",
+ 0 /* visible */, true /* auto-add */, false /* fav */);
+ long contactId2 = queryContactId(createRawContactWithName("Helen", "Parr",
+ account));
+
+ Uri contentUri = Contacts.CONTENT_URI.buildUpon().appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.LOCAL_INVISIBLE))
+ .build();
+
+ Cursor cursor = mResolver.query(contentUri,
+ new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ // Hide by removing from the default group
+ mResolver.delete(Data.CONTENT_URI,
+ Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
+ new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId) });
+
+ cursor = mResolver.query(contentUri,
+ new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(contactId2, cursor.getLong(0));
+ assertEquals("Helen Parr", cursor.getString(1));
+ cursor.close();
+
+ Uri filterUri = Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath("parr")
+ .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(Directory.LOCAL_INVISIBLE)).build();
+
+ cursor = mResolver.query(filterUri,
+ new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(contactId2, cursor.getLong(0));
+ assertEquals("Helen Parr", cursor.getString(1));
+ cursor.close();
+ }
+}
+
diff --git a/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java b/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java
index 3919770..50ba389 100644
--- a/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java
+++ b/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java
@@ -18,6 +18,7 @@
import android.content.ContentProvider;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.Debug;
import android.provider.CallLog;
@@ -72,6 +73,13 @@
public String getPackageName() {
return "no.package";
}
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = "contactsTestPackage";
+ return ai;
+ }
};
RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
targetContext, "perf_imp.");
diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
index 85ed6a7..d2e4365 100644
--- a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
+++ b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
@@ -392,19 +392,22 @@
values.put(Phones.ISPRIMARY, 1);
Uri uri = mResolver.insert(Phones.CONTENT_URI, values);
-
+ ContentValues expectedResults[] = new ContentValues[2];
// Adding another value to assert
- values.put(Phones.NUMBER_KEY, "11446640081");
-
+ expectedResults[0] = new ContentValues(values);
+ expectedResults[0].put(Phones.NUMBER_KEY, "+18004664411");
+ expectedResults[1] = values;
+ expectedResults[1].put(Phones.NUMBER_KEY, "18004664411");
// The result is joined with People
- putContactValues(values);
- assertStoredValues(uri, values);
+ putContactValues(expectedResults[0]);
+ putContactValues(expectedResults[1]);
+ assertStoredValues(uri, expectedResults);
assertSelection(Phones.CONTENT_URI, values, "phones",
Phones._ID, ContentUris.parseId(uri));
// Access the phone through People
Uri twigUri = Uri.withAppendedPath(personUri, People.Phones.CONTENT_DIRECTORY);
- assertStoredValues(twigUri, values);
+ assertStoredValues(twigUri, expectedResults);
// Now the person should be joined with Phone
values.clear();
@@ -443,7 +446,12 @@
mResolver.update(uri, values, null, null);
- assertStoredValues(uri, values);
+ ContentValues[] expectedValues = new ContentValues[2];
+ expectedValues[0] = values;
+ expectedValues[0].put(Phones.NUMBER_KEY, "18005554663");
+ expectedValues[1] = new ContentValues(values);
+ expectedValues[1].put(Phones.NUMBER_KEY, "+18005554663");
+ assertStoredValues(uri, expectedValues);
}
public void testPhonesFilterQuery() {
@@ -456,13 +464,18 @@
values.put(Phones.PERSON_ID, personId);
values.put(Phones.TYPE, Phones.TYPE_CUSTOM);
values.put(Phones.LABEL, "Directory");
- values.put(Phones.NUMBER, "1-800-4664-411");
+ values.put(Phones.NUMBER, "800-4664-411");
values.put(Phones.ISPRIMARY, 1);
Uri uri = mResolver.insert(Phones.CONTENT_URI, values);
- Uri filterUri1 = Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, "8004664411");
- assertStoredValues(filterUri1, values);
+ Uri filterUri1 = Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, "18004664411");
+ ContentValues[] expectedValues = new ContentValues[2];
+ expectedValues[0] = values;
+ expectedValues[0].put(Phones.NUMBER_KEY, "8004664411");
+ expectedValues[1] = new ContentValues(values);
+ expectedValues[1].put(Phones.NUMBER_KEY, "+18004664411");
+ assertStoredValues(filterUri1, expectedValues);
Uri filterUri2 = Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, "7773334444");
assertEquals(0, getCount(filterUri2, null, null));
diff --git a/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java b/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java
index 1de34c0..91ab761 100644
--- a/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java
+++ b/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java
@@ -200,6 +200,40 @@
"(6:C)", mBuilder.inserted());
}
+ public void testKoreanName() {
+ // Only run this test when Chinese collation is supported.
+ if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.KOREA)) {
+ return;
+ }
+
+ // Lee Sang Il
+ mBuilder.insertNameLookup(0, 0, "\uC774\uC0C1\uC77C", FullNameStyle.KOREAN);
+ assertEquals(
+ "(0:\uC774\uC0C1\uC77C)" + // Lee Sang Il
+ "(2:\uC774\uC0C1\uC77C)" + // Lee Sang Il
+ "(6:\uC0C1\uC77C)" + // Sang Il : given name
+ "(7:\u1109\u110B)" + // SIOS IEUNG : consonants of given name
+ "(7:\u110B\u1109\u110B)", // RIEUL SIOS IEUNG : consonants of fullname
+ mBuilder.inserted());
+ }
+
+ public void testKoreanNameWithTwoCharactersFamilyName() {
+ // Only run this test when Chinese collation is supported.
+ if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.KOREA)) {
+ return;
+ }
+
+ // Sun Woo Young Nyeu
+ mBuilder.insertNameLookup(0, 0, "\uC120\uC6B0\uC6A9\uB140", FullNameStyle.KOREAN);
+ assertEquals(
+ "(0:\uC120\uC6B0\uC6A9\uB140)" + // Sun Woo Young Nyeu
+ "(2:\uC120\uC6B0\uC6A9\uB140)" + // Sun Woo Young Nyeu
+ "(6:\uC6A9\uB140)" + // Young Nyeu : given name
+ "(7:\u110B\u1102)" + // IEUNG NIEUN : consonants of given name
+ "(7:\u1109\u110B\u110B\u1102)", // SIOS IEUNG IEUNG NIEUN : consonants of fullname
+ mBuilder.inserted());
+ }
+
public void testMultiwordName() {
mBuilder.insertNameLookup(0, 0, "Jo Jeffrey John Jessy Longname", FullNameStyle.UNDEFINED);
String actual = mBuilder.inserted();
diff --git a/tests/src/com/android/providers/contacts/NameSplitterTest.java b/tests/src/com/android/providers/contacts/NameSplitterTest.java
index 87c897e..d656b04 100644
--- a/tests/src/com/android/providers/contacts/NameSplitterTest.java
+++ b/tests/src/com/android/providers/contacts/NameSplitterTest.java
@@ -372,4 +372,14 @@
assertEquals(expectedPhoneticNameStyle, name.phoneticNameStyle);
}
+
+ public void testSplitKoreanName() {
+ createNameSplitter(Locale.KOREA);
+
+ // Lee - Sang Il
+ assertSplitName("\uC774\uC0C1\uC77C", null, "\uC0C1\uC77C", null, "\uC774", null);
+ // Dok Go - Young Jae
+ assertSplitName("\uB3C5\uACE0\uC601\uC7AC",
+ null, "\uC601\uC7AC", null, "\uB3C5\uACE0", null);
+ }
}
diff --git a/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java b/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java
index 6209e54..3fbf548 100644
--- a/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java
+++ b/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java
@@ -18,6 +18,7 @@
import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
import static com.android.providers.contacts.ContactsActor.PACKAGE_RED;
+
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@@ -35,6 +36,8 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.InputStream;
/**
@@ -190,9 +193,7 @@
final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
final AssetFileDescriptor file = mRed.resolver.openAssetFileDescriptor(shareUri, "r");
final InputStream in = file.createInputStream();
- final byte[] buf = new byte[in.available()];
- in.read(buf);
- in.close();
+ final byte[] buf = readInputStream(in);
final String card = new String(buf);
assertNotSame(0, card.length());
@@ -201,6 +202,20 @@
assertTrue(card.indexOf(PHONE_GREY) == -1);
}
+ private static byte[] readInputStream(InputStream in) throws IOException {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int count;
+ while ((count = in.read(buf)) != -1) {
+ out.write(buf, 0, count);
+ }
+ return out.toByteArray();
+ } finally {
+ in.close();
+ }
+ }
+
public void testContactsLiveFolder() {
final long greyContact = mGrey.createContact(true, GENERIC_NAME);
final long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY);
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index 0248094..a280bb2 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -38,7 +38,12 @@
@Override
protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
if (mDbHelper == null) {
- mDbHelper = new ContactsDatabaseHelper(context);
+ mDbHelper = new ContactsDatabaseHelper(context) {
+ @Override
+ protected String getCountryIso() {
+ return "US";
+ }
+ };
}
return mDbHelper;
}
@@ -89,6 +94,10 @@
}
@Override
+ protected void startContactDirectoryManager() {
+ }
+
+ @Override
protected Account getDefaultAccount() {
if (mAccount == null) {
mAccount = new Account("androidtest@gmail.com", "com.google");
@@ -162,4 +171,9 @@
// We have an explicit test for data conversion - no need to do it every time
return false;
}
+
+ @Override
+ protected String getCurrentCountryIso() {
+ return "us";
+ }
}